1 /// Translated from C to D
2 module soundio.os;
3 
4 extern(C): @nogc: nothrow: __gshared:
5 
6 public import soundio.soundio_internal;
7 public import soundio.util;
8 
9 import core.stdc.stdlib: free;
10 import core.stdc..string;
11 import core.stdc.errno;
12 
13 // You may rely on the size of this struct as part of the API and ABI.
14 struct SoundIoOsMirroredMemory {
15     size_t capacity;
16     char* address;
17     void* priv;
18 }
19 
20 version (Windows) {
21     public import core.sys.windows.windows;
22     public import core.sys.windows.mmsystem;
23     public import core.sys.windows.objbase;
24 
25     alias INIT_ONCE = void*;
26     alias CONDITION_VARIABLE = void*;
27     enum INIT_ONCE_STATIC_INIT = null;
28 
29     enum COINITBASE_MULTITHREADED = 0;
30     alias COINIT = int; // C enum
31     enum {
32         COINIT_APARTMENTTHREADED  = 0x2,
33         COINIT_MULTITHREADED      = COINITBASE_MULTITHREADED,
34         COINIT_DISABLE_OLE1DDE    = 0x4,
35         COINIT_SPEED_OVER_MEMORY  = 0x8,
36     }
37 
38     extern(Windows) {
39         BOOL SleepConditionVariableCS(CONDITION_VARIABLE* ConditionVariable, PCRITICAL_SECTION CriticalSection, DWORD dwMilliseconds);
40         HRESULT CoInitializeEx(LPVOID, DWORD);
41         void CoUninitialize();
42         void InitializeConditionVariable(CONDITION_VARIABLE* ConditionVariable);
43         void WakeConditionVariable(CONDITION_VARIABLE* ConditionVariable);
44         BOOL InitOnceBeginInitialize(INIT_ONCE* lpInitOnce, DWORD dwFlags, BOOL* fPending, VOID** lpContext);
45         BOOL InitOnceComplete(INIT_ONCE* lpInitOnce, DWORD dwFlags, VOID* lpContext);
46         enum INIT_ONCE_ASYNC = 0x00000002UL;
47     }
48 
49 } else {
50     public import core.sys.posix.pthread;
51     public import core.sys.posix.unistd;
52     public import core.sys.posix.sys.mman;
53     enum MAP_ANONYMOUS = MAP_ANON;
54 
55     // commented out of core.sys.posix.stdlib
56     extern(C) @nogc nothrow int mkstemp(char*);
57 }
58 
59 version(FreeBSD) {
60     version = SOUNDIO_OS_KQUEUE;
61 }
62 version(OSX) {
63     version = SOUNDIO_OS_KQUEUE;
64 }
65 
66 version(SOUNDIO_OS_KQUEUE) {
67     public import core.sys.posix.sys.types;
68     public import core.sys.posix.sys.time;
69 }
70 
71 version (OSX) {
72     public import mach.clock;
73     public import mach.mach;
74 }
75 
76 struct SoundIoOsThread {
77     version(Windows) {
78         HANDLE handle;
79         DWORD id;
80     } else {
81         pthread_attr_t attr;
82         bool attr_init;
83 
84         pthread_t id;
85         bool running;
86     }
87     void* arg;
88     extern(C) @nogc nothrow void function(void* arg) run;
89 }
90 
91 struct SoundIoOsMutex {
92     version(Windows) {
93         CRITICAL_SECTION id;
94     } else {
95         pthread_mutex_t id;
96         bool id_init;
97     }
98 }
99 
100 version (SOUNDIO_OS_KQUEUE) {
101     static const(uintptr_t) notify_ident = 1;
102     struct SoundIoOsCond {
103         int kq_id;
104     }
105 } else version(Windows) {
106     struct SoundIoOsCond {
107         CONDITION_VARIABLE id;
108         CRITICAL_SECTION default_cs_id;
109     }
110 } else {
111     struct SoundIoOsCond {
112         pthread_cond_t id;
113         bool id_init;
114 
115         pthread_condattr_t attr;
116         bool attr_init;
117 
118         pthread_mutex_t default_mutex_id;
119         bool default_mutex_init;
120     }
121 }
122 
123 version(Windows) {
124     static INIT_ONCE win32_init_once = INIT_ONCE_STATIC_INIT;
125     static double win32_time_resolution;
126     static SYSTEM_INFO win32_system_info;
127 } else {
128     static bool initialized = false;
129     static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
130     version (OSX) {
131         static clock_serv_t cclock;
132     }
133 }
134 
135 static int page_size;
136 
137 double soundio_os_get_time() {
138     version(Windows) {
139         ulong time;
140         QueryPerformanceCounter(cast(LARGE_INTEGER*) &time);
141         return time * win32_time_resolution;
142     } else version (OSX) {
143         mach_timespec_t mts;
144 
145         kern_return_t err = clock_get_time(cclock, &mts);
146         assert(!err);
147 
148         double seconds = cast(double)mts.tv_sec;
149         seconds += (cast(double)mts.tv_nsec) / 1000000000.0;
150 
151         return seconds;
152     } else {
153         timespec tms;
154         clock_gettime(CLOCK_MONOTONIC, &tms);
155         double seconds = cast(double)tms.tv_sec;
156         seconds += (cast(double)tms.tv_nsec) / 1000000000.0;
157         return seconds;
158     }
159 }
160 
161 version(Windows) {
162     extern(Windows) static DWORD run_win32_thread(LPVOID userdata) {
163         SoundIoOsThread* thread = cast(SoundIoOsThread*)userdata;
164         HRESULT err = CoInitializeEx(null, COINIT_APARTMENTTHREADED);
165         assert(err == S_OK);
166         thread.run(thread.arg);
167         CoUninitialize();
168         return 0;
169     }
170 } else {
171     static void assert_no_err(int err) {
172         assert(!err);
173     }
174 
175     static void* run_pthread(void* userdata) {
176         SoundIoOsThread* thread = cast(SoundIoOsThread*)userdata;
177         thread.run(thread.arg);
178         return null;
179     }
180 }
181 
182 private alias threadFunc = void function(void* arg);
183 private alias warningFunc = void function();
184 int soundio_os_thread_create(threadFunc run, void* arg, warningFunc emit_rtprio_warning, SoundIoOsThread** out_thread) {
185     *out_thread = null;
186 
187     SoundIoOsThread* thread = ALLOCATE!SoundIoOsThread(1);
188     if (!thread) {
189         soundio_os_thread_destroy(thread);
190         return SoundIoError.NoMem;
191     }
192 
193     thread.run = run;
194     thread.arg = arg;
195 
196 version(Windows) {
197     thread.handle = CreateThread(null, 0, &run_win32_thread, thread, 0, &thread.id);
198     if (!thread.handle) {
199         soundio_os_thread_destroy(thread);
200         return SoundIoError.SystemResources;
201     }
202     if (emit_rtprio_warning) {
203         if (!SetThreadPriority(thread.handle, THREAD_PRIORITY_TIME_CRITICAL)) {
204             emit_rtprio_warning();
205         }
206     }
207 } else {
208     if (auto err = pthread_attr_init(&thread.attr)) {
209         soundio_os_thread_destroy(thread);
210         return SoundIoError.NoMem;
211     }
212     thread.attr_init = true;
213 
214     if (emit_rtprio_warning) {
215         int max_priority = sched_get_priority_max(SCHED_FIFO);
216         if (max_priority == -1) {
217             soundio_os_thread_destroy(thread);
218             return SoundIoError.SystemResources;
219         }
220 
221         if (auto err = pthread_attr_setschedpolicy(&thread.attr, SCHED_FIFO)) {
222             soundio_os_thread_destroy(thread);
223             return SoundIoError.SystemResources;
224         }
225 
226         sched_param param;
227         param.sched_priority = max_priority;
228         if (auto err = pthread_attr_setschedparam(&thread.attr, &param)) {
229             soundio_os_thread_destroy(thread);
230             return SoundIoError.SystemResources;
231         }
232 
233     }
234 
235     if (auto err = pthread_create(&thread.id, &thread.attr, &run_pthread, thread)) {
236         if (err == EPERM && emit_rtprio_warning) {
237             emit_rtprio_warning();
238             err = pthread_create(&thread.id, null, &run_pthread, thread);
239         }
240         if (err) {
241             soundio_os_thread_destroy(thread);
242             return SoundIoError.NoMem;
243         }
244     }
245     thread.running = true;
246 }
247 
248     *out_thread = thread;
249     return 0;
250 }
251 
252 void soundio_os_thread_destroy(SoundIoOsThread* thread) {
253     if (!thread)
254         return;
255 
256 version(Windows) {
257     if (thread.handle) {
258         DWORD err = WaitForSingleObject(thread.handle, INFINITE);
259         assert(err != WAIT_FAILED);
260         BOOL ok = CloseHandle(thread.handle);
261         assert(ok);
262     }
263 } else {
264 
265     if (thread.running) {
266         assert_no_err(pthread_join(thread.id, null));
267     }
268 
269     if (thread.attr_init) {
270         assert_no_err(pthread_attr_destroy(&thread.attr));
271     }
272 }
273 
274     free(thread);
275 }
276 
277 SoundIoOsMutex* soundio_os_mutex_create() {
278     SoundIoOsMutex* mutex = ALLOCATE!SoundIoOsMutex( 1);
279     if (!mutex) {
280         soundio_os_mutex_destroy(mutex);
281         return null;
282     }
283 
284 version(Windows) {
285     InitializeCriticalSection(&mutex.id);
286 } else {
287     if (auto err = pthread_mutex_init(&mutex.id, null)) {
288         soundio_os_mutex_destroy(mutex);
289         return null;
290     }
291     mutex.id_init = true;
292 }
293 
294     return mutex;
295 }
296 
297 void soundio_os_mutex_destroy(SoundIoOsMutex* mutex) {
298     if (!mutex)
299         return;
300 
301 version(Windows) {
302     DeleteCriticalSection(&mutex.id);
303 } else {
304     if (mutex.id_init) {
305         assert_no_err(pthread_mutex_destroy(&mutex.id));
306     }
307 }
308 
309     free(mutex);
310 }
311 
312 void soundio_os_mutex_lock(SoundIoOsMutex* mutex) {
313 version(Windows) {
314     EnterCriticalSection(&mutex.id);
315 } else {
316     assert_no_err(pthread_mutex_lock(&mutex.id));
317 }
318 }
319 
320 void soundio_os_mutex_unlock(SoundIoOsMutex* mutex) {
321 version(Windows) {
322     LeaveCriticalSection(&mutex.id);
323 } else {
324     assert_no_err(pthread_mutex_unlock(&mutex.id));
325 }
326 }
327 
328 SoundIoOsCond* soundio_os_cond_create() {
329     SoundIoOsCond* cond = ALLOCATE!SoundIoOsCond(1);
330 
331     if (!cond) {
332         soundio_os_cond_destroy(cond);
333         return null;
334     }
335 
336 version(Windows) {
337     InitializeConditionVariable(&cond.id);
338     InitializeCriticalSection(&cond.default_cs_id);
339 } else version (SOUNDIO_OS_KQUEUE) {
340     cond.kq_id = kqueue();
341     if (cond.kq_id == -1)
342         return null;
343 } else {
344     if (pthread_condattr_init(&cond.attr)) {
345         soundio_os_cond_destroy(cond);
346         return null;
347     }
348     cond.attr_init = true;
349 
350     if (pthread_condattr_setclock(&cond.attr, CLOCK_MONOTONIC)) {
351         soundio_os_cond_destroy(cond);
352         return null;
353     }
354 
355     if (pthread_cond_init(&cond.id, &cond.attr)) {
356         soundio_os_cond_destroy(cond);
357         return null;
358     }
359     cond.id_init = true;
360 
361     if ((pthread_mutex_init(&cond.default_mutex_id, null))) {
362         soundio_os_cond_destroy(cond);
363         return null;
364     }
365     cond.default_mutex_init = true;
366 }
367 
368     return cond;
369 }
370 
371 void soundio_os_cond_destroy(SoundIoOsCond* cond) {
372     if (!cond)
373         return;
374 
375 version(Windows) {
376     DeleteCriticalSection(&cond.default_cs_id);
377 } else version (SOUNDIO_OS_KQUEUE) {
378     close(cond.kq_id);
379 } else {
380     if (cond.id_init) {
381         assert_no_err(pthread_cond_destroy(&cond.id));
382     }
383 
384     if (cond.attr_init) {
385         assert_no_err(pthread_condattr_destroy(&cond.attr));
386     }
387     if (cond.default_mutex_init) {
388         assert_no_err(pthread_mutex_destroy(&cond.default_mutex_id));
389     }
390 }
391 
392     free(cond);
393 }
394 
395 void soundio_os_cond_signal(SoundIoOsCond* cond, SoundIoOsMutex* locked_mutex) {
396 version(Windows) {
397     if (locked_mutex) {
398         WakeConditionVariable(&cond.id);
399     } else {
400         EnterCriticalSection(&cond.default_cs_id);
401         WakeConditionVariable(&cond.id);
402         LeaveCriticalSection(&cond.default_cs_id);
403     }
404 } else version (SOUNDIO_OS_KQUEUE) {
405     kevent kev;
406     timespec timeout = [ 0, 0 ];
407 
408     memset(&kev, 0, kev.sizeof);
409     kev.ident = notify_ident;
410     kev.filter = EVFILT_USER;
411     kev.fflags = NOTE_TRIGGER;
412 
413     if (kevent(cond.kq_id, &kev, 1, null, 0, &timeout) == -1) {
414         if (errno == EINTR)
415             return;
416         if (errno == ENOENT)
417             return;
418         assert(0); // kevent signal error
419     }
420 } else {
421     if (locked_mutex) {
422         assert_no_err(pthread_cond_signal(&cond.id));
423     } else {
424         assert_no_err(pthread_mutex_lock(&cond.default_mutex_id));
425         assert_no_err(pthread_cond_signal(&cond.id));
426         assert_no_err(pthread_mutex_unlock(&cond.default_mutex_id));
427     }
428 }
429 }
430 
431 void soundio_os_cond_timed_wait(SoundIoOsCond* cond, SoundIoOsMutex* locked_mutex, double seconds) {
432 version(Windows) {
433     CRITICAL_SECTION* target_cs;
434     if (locked_mutex) {
435         target_cs = &locked_mutex.id;
436     } else {
437         target_cs = &cond.default_cs_id;
438         EnterCriticalSection(&cond.default_cs_id);
439     }
440     DWORD ms = cast(int) (seconds * 1000.0);
441     SleepConditionVariableCS(&cond.id, target_cs, ms);
442     if (!locked_mutex)
443         LeaveCriticalSection(&cond.default_cs_id);
444 } else version (SOUNDIO_OS_KQUEUE) {
445     kevent kev;
446     kevent out_kev;
447 
448     if (locked_mutex)
449         assert_no_err(pthread_mutex_unlock(&locked_mutex.id));
450 
451     memset(&kev, 0, kev.sizeof);
452     kev.ident = notify_ident;
453     kev.filter = EVFILT_USER;
454     kev.flags = EV_ADD | EV_CLEAR;
455 
456     // this time is relative
457     timespec timeout;
458     timeout.tv_nsec = (seconds * 1000000000L);
459     timeout.tv_sec  = timeout.tv_nsec / 1000000000L;
460     timeout.tv_nsec = timeout.tv_nsec % 1000000000L;
461 
462     if (kevent(cond.kq_id, &kev, 1, &out_kev, 1, &timeout) == -1) {
463         if (errno == EINTR)
464             return;
465         assert(0); // kevent wait error
466     }
467     if (locked_mutex)
468         assert_no_err(pthread_mutex_lock(&locked_mutex.id));
469 } else {
470     pthread_mutex_t* target_mutex;
471     if (locked_mutex) {
472         target_mutex = &locked_mutex.id;
473     } else {
474         target_mutex = &cond.default_mutex_id;
475         assert_no_err(pthread_mutex_lock(target_mutex));
476     }
477     // this time is absolute
478     timespec tms;
479     clock_gettime(CLOCK_MONOTONIC, &tms);
480     tms.tv_nsec += cast(long) (seconds * 1000000000L);
481     tms.tv_sec += tms.tv_nsec / 1000000000L;
482     tms.tv_nsec = tms.tv_nsec % 1000000000L;
483     if (auto err = pthread_cond_timedwait(&cond.id, target_mutex, &tms)) {
484         assert(err != EPERM);
485         assert(err != EINVAL);
486     }
487     if (!locked_mutex)
488         assert_no_err(pthread_mutex_unlock(target_mutex));
489 }
490 }
491 
492 void soundio_os_cond_wait(SoundIoOsCond* cond, SoundIoOsMutex* locked_mutex) {
493 version(Windows) {
494     CRITICAL_SECTION* target_cs;
495     if (locked_mutex) {
496         target_cs = &locked_mutex.id;
497     } else {
498         target_cs = &cond.default_cs_id;
499         EnterCriticalSection(&cond.default_cs_id);
500     }
501     SleepConditionVariableCS(&cond.id, target_cs, INFINITE);
502     if (!locked_mutex)
503         LeaveCriticalSection(&cond.default_cs_id);
504 } else version (SOUNDIO_OS_KQUEUE) {
505     kevent kev;
506     kevent out_kev;
507 
508     if (locked_mutex)
509         assert_no_err(pthread_mutex_unlock(&locked_mutex.id));
510 
511     memset(&kev, 0, kev.sizeof);
512     kev.ident = notify_ident;
513     kev.filter = EVFILT_USER;
514     kev.flags = EV_ADD | EV_CLEAR;
515 
516     if (kevent(cond.kq_id, &kev, 1, &out_kev, 1, null) == -1) {
517         if (errno == EINTR)
518             return;
519         assert(0); // kevent wait error
520     }
521     if (locked_mutex)
522         assert_no_err(pthread_mutex_lock(&locked_mutex.id));
523 } else {
524     pthread_mutex_t* target_mutex;
525     if (locked_mutex) {
526         target_mutex = &locked_mutex.id;
527     } else {
528         target_mutex = &cond.default_mutex_id;
529         assert_no_err(pthread_mutex_lock(&cond.default_mutex_id));
530     }
531     if (auto err = pthread_cond_wait(&cond.id, target_mutex)) {
532         assert(err != EPERM);
533         assert(err != EINVAL);
534     }
535     if (!locked_mutex)
536         assert_no_err(pthread_mutex_unlock(&cond.default_mutex_id));
537 }
538 }
539 
540 static int internal_init() {
541 version(Windows) {
542     ulong frequency;
543     if (QueryPerformanceFrequency(cast(LARGE_INTEGER*) &frequency)) {
544         win32_time_resolution = 1.0 / cast(double) frequency;
545     } else {
546         return SoundIoError.SystemResources;
547     }
548     GetSystemInfo(&win32_system_info);
549     page_size = win32_system_info.dwAllocationGranularity;
550 } else {
551     page_size = cast(int) sysconf(_SC_PAGESIZE);
552     version (OSX) {
553         host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
554     }
555 }
556     return 0;
557 }
558 
559 int soundio_os_init() {
560 version(Windows) {
561     PVOID lpContext;
562     BOOL pending;
563 
564     if (!InitOnceBeginInitialize(&win32_init_once, INIT_ONCE_ASYNC, &pending, &lpContext))
565         return SoundIoError.SystemResources;
566 
567     if (!pending)
568         return 0;
569 
570     if (auto err = internal_init())
571         return err;
572 
573     if (!InitOnceComplete(&win32_init_once, INIT_ONCE_ASYNC, null))
574         return SoundIoError.SystemResources;
575 } else {
576     assert_no_err(pthread_mutex_lock(&init_mutex));
577     if (initialized) {
578         assert_no_err(pthread_mutex_unlock(&init_mutex));
579         return 0;
580     }
581     initialized = true;
582     if (auto err = internal_init())
583         return err;
584     assert_no_err(pthread_mutex_unlock(&init_mutex));
585 }
586 
587     return 0;
588 }
589 
590 int soundio_os_page_size() {
591     return page_size;
592 }
593 
594 pragma(inline, true) static size_t ceil_dbl_to_size_t(double x) {
595     const(double) truncation = cast(size_t)x;
596     return cast(size_t) (truncation + (truncation < x));
597 }
598 
599 int soundio_os_init_mirrored_memory(SoundIoOsMirroredMemory* mem, size_t requested_capacity) {
600     size_t actual_capacity = ceil_dbl_to_size_t(requested_capacity / cast(double)page_size) * page_size;
601 
602     version(Windows) {
603         BOOL ok;
604         HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, null, PAGE_READWRITE, 0, cast(int) (actual_capacity * 2), null);
605         if (!hMapFile)
606             return SoundIoError.NoMem;
607 
608         for (;;) {
609             // find a free address space with the correct size
610             char* address = cast(char*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, actual_capacity * 2);
611             if (!address) {
612                 ok = CloseHandle(hMapFile);
613                 assert(ok);
614                 return SoundIoError.NoMem;
615             }
616 
617             // found a big enough address space. hopefully it will remain free
618             // while we map to it. if not, we'll try again.
619             ok = UnmapViewOfFile(address);
620             assert(ok);
621 
622             char* addr1 = cast(char*)MapViewOfFileEx(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, actual_capacity, address);
623             if (addr1 != address) {
624                 DWORD err = GetLastError();
625                 if (err == ERROR_INVALID_ADDRESS) {
626                     continue;
627                 } else {
628                     ok = CloseHandle(hMapFile);
629                     assert(ok);
630                     return SoundIoError.NoMem;
631                 }
632             }
633 
634             char* addr2 = cast(char*)MapViewOfFileEx(hMapFile, FILE_MAP_WRITE, 0, 0,
635                     actual_capacity, address + actual_capacity);
636             if (addr2 != address + actual_capacity) {
637                 ok = UnmapViewOfFile(addr1);
638                 assert(ok);
639 
640                 DWORD err = GetLastError();
641                 if (err == ERROR_INVALID_ADDRESS) {
642                     continue;
643                 } else {
644                     ok = CloseHandle(hMapFile);
645                     assert(ok);
646                     return SoundIoError.NoMem;
647                 }
648             }
649 
650             mem.priv = hMapFile;
651             mem.address = address;
652             break;
653         }
654     } else {
655         char[32] shm_path = "/dev/shm/soundio-XXXXXX\0";
656         char[32] tmp_path = "/tmp/soundio-XXXXXX\0";
657         char* chosen_path;
658 
659         int fd = mkstemp(shm_path.ptr);
660         if (fd < 0) {
661             fd = mkstemp(tmp_path.ptr);
662             if (fd < 0) {
663                 return SoundIoError.SystemResources;
664             } else {
665                 chosen_path = tmp_path.ptr;
666             }
667         } else {
668             chosen_path = shm_path.ptr;
669         }
670 
671         if (unlink(chosen_path)) {
672             close(fd);
673             return SoundIoError.SystemResources;
674         }
675 
676         if (ftruncate(fd, actual_capacity)) {
677             close(fd);
678             return SoundIoError.SystemResources;
679         }
680 
681         char* address = cast(char*)mmap(null, actual_capacity * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
682         if (address == MAP_FAILED) {
683             close(fd);
684             return SoundIoError.NoMem;
685         }
686 
687         char* other_address = cast(char*)mmap(address, actual_capacity, PROT_READ|PROT_WRITE,
688                 MAP_FIXED|MAP_SHARED, fd, 0);
689         if (other_address != address) {
690             munmap(address, 2 * actual_capacity);
691             close(fd);
692             return SoundIoError.NoMem;
693         }
694 
695         other_address = cast(char*)mmap(address + actual_capacity, actual_capacity,
696                 PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0);
697         if (other_address != address + actual_capacity) {
698             munmap(address, 2 * actual_capacity);
699             close(fd);
700             return SoundIoError.NoMem;
701         }
702 
703         mem.address = address;
704 
705         if (close(fd))
706             return SoundIoError.SystemResources;
707     }
708 
709     mem.capacity = actual_capacity;
710     return 0;
711 }
712 
713 void soundio_os_deinit_mirrored_memory(SoundIoOsMirroredMemory* mem) {
714     if (!mem.address)
715         return;
716     version(Windows) {
717         BOOL ok;
718         ok = UnmapViewOfFile(mem.address);
719         assert(ok);
720         ok = UnmapViewOfFile(mem.address + mem.capacity);
721         assert(ok);
722         ok = CloseHandle(cast(HANDLE)mem.priv);
723         assert(ok);
724     } else {
725         int err = munmap(mem.address, 2 * mem.capacity);
726         assert(!err);
727     }
728     mem.address = null;
729 }