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