1 /++ 2 $(PITFALL 3 Please note: the api and behavior of this module is not externally stable at this time. See the documentation on specific functions for details. 4 ) 5 6 Shared core functionality including exception helpers, library loader, event loop, and possibly more. Maybe command line processor and uda helper and some basic shared annotation types. 7 8 I'll probably move the url, websocket, and ssl stuff in here too as they are often shared. Maybe a small internationalization helper type (a hook for external implementation) and COM helpers too. I might move the process helpers out to their own module - even things in here are not considered stable to library users at this time! 9 10 If you use this directly outside the arsd library despite its current instability caveats, you might consider using `static import` since names in here are likely to clash with Phobos if you use them together. `static import` will let you easily disambiguate and avoid name conflict errors if I add more here. Some names even clash deliberately to remind me to avoid some antipatterns inside the arsd modules! 11 12 ## Contributor notes 13 14 arsd.core should be focused on things that enable interoperability primarily and secondarily increased code quality between other, otherwise independent arsd modules. As a foundational library, it is not permitted to import anything outside the druntime `core` namespace, except in templates and examples not normally compiled in. This keeps it independent and avoids transitive dependency spillover to end users while also keeping compile speeds fast. To help keep builds snappy, also avoid significant use of ctfe inside this module. 15 16 On my linux computer, `dmd -unittest -main core.d` takes about a quarter second to run. We do not want this to grow. 17 18 `@safe` compatibility is ok when it isn't too big of a hassle. `@nogc` is a non-goal. I might accept it on some of the trivial functions but if it means changing the logic in any way to support, you will need a compelling argument to justify it. The arsd libs are supposed to be reliable and easy to use. That said, of course, don't be unnecessarily wasteful - if you can easily provide a reliable and easy to use way to let advanced users do their thing without hurting the other cases, let's discuss it. 19 20 If functionality is not needed by multiple existing arsd modules, consider adding a new module instead of adding it to the core. 21 22 Unittests should generally be hidden behind a special version guard so they don't interfere with end user tests. 23 24 History: 25 Added March 2023 (dub v11.0). Several functions were migrated in here at that time, noted individually. Members without a note were added with the module. 26 +/ 27 module arsd.core; 28 29 // FIXME: add callbacks on file open for tracing dependencies dynamically 30 31 // see for useful info: https://devblogs.microsoft.com/dotnet/how-async-await-really-works/ 32 33 // see: https://wiki.openssl.org/index.php/Simple_TLS_Server 34 35 // see: When you only want to track changes on a file or directory, be sure to open it using the O_EVTONLY flag. 36 37 ///ArsdUseCustomRuntime is used since other derived work from WebAssembly may be used and thus specified in the CLI 38 version(WebAssembly) version = ArsdUseCustomRuntime; 39 40 // note that kqueue might run an i/o loop on mac, ios, etc. but then NSApp.run on the io thread 41 // but on bsd, you want the kqueue loop in i/o too.... 42 43 version(iOS) 44 { 45 version = EmptyEventLoop; 46 version = EmptyCoreEvent; 47 } 48 version(ArsdUseCustomRuntime) 49 { 50 version = EmptyEventLoop; 51 version = UseStdioWriteln; 52 } 53 else 54 { 55 version = HasFile; 56 version = HasSocket; 57 version = HasThread; 58 version = HasErrno; 59 } 60 61 version(HasThread) 62 { 63 import core.thread; 64 import core..volatile; 65 import core.atomic; 66 import core.time; 67 } 68 else 69 { 70 // polyfill for missing core.time 71 struct Duration { 72 static Duration max() { return Duration(); } 73 } 74 } 75 76 version(OSX) { 77 version(ArsdNoCocoa) 78 enum bool UseCocoa = false; 79 else 80 enum bool UseCocoa = true; 81 } 82 83 version(HasErrno) 84 import core.stdc.errno; 85 86 import core.attribute; 87 static if(!__traits(hasMember, core.attribute, "mustuse")) 88 enum mustuse; 89 90 // FIXME: add an arena allocator? can do task local destruction maybe. 91 92 // the three implementations are windows, epoll, and kqueue 93 version(Windows) { 94 version=Arsd_core_windows; 95 96 // import core.sys.windows.windows; 97 import core.sys.windows.winbase; 98 import core.sys.windows.windef; 99 import core.sys.windows.winnls; 100 import core.sys.windows.winuser; 101 import core.sys.windows.winsock2; 102 103 pragma(lib, "user32"); 104 pragma(lib, "ws2_32"); 105 } else version(linux) { 106 version=Arsd_core_epoll; 107 108 static if(__VERSION__ >= 2098) { 109 version=Arsd_core_has_cloexec; 110 } 111 } else version(FreeBSD) { 112 version=Arsd_core_kqueue; 113 114 import core.sys.freebsd.sys.event; 115 } else version(DragonFlyBSD) { 116 // NOT ACTUALLY TESTED 117 version=Arsd_core_kqueue; 118 119 import core.sys.dragonflybsd.sys.event; 120 } else version(NetBSD) { 121 // NOT ACTUALLY TESTED 122 version=Arsd_core_kqueue; 123 124 import core.sys.netbsd.sys.event; 125 } else version(OpenBSD) { 126 version=Arsd_core_kqueue; 127 128 // THIS FILE DOESN'T ACTUALLY EXIST, WE NEED TO MAKE IT 129 import core.sys.openbsd.sys.event; 130 } else version(OSX) { 131 version=Arsd_core_kqueue; 132 133 import core.sys.darwin.sys.event; 134 135 version(DigitalMars) { 136 version=OSXCocoa; 137 } 138 } 139 140 version(OSXCocoa) 141 enum CocoaAvailable = true; 142 else 143 enum CocoaAvailable = false; 144 145 version(Posix) { 146 import core.sys.posix.signal; 147 import core.sys.posix.unistd; 148 149 import core.sys.posix.sys.un; 150 import core.sys.posix.sys.socket; 151 import core.sys.posix.netinet.in_; 152 } 153 154 // FIXME: the exceptions should actually give some explanatory text too (at least sometimes) 155 156 /+ 157 ========================= 158 GENERAL UTILITY FUNCTIONS 159 ========================= 160 +/ 161 162 // enum stringz : const(char)* { init = null } 163 164 /++ 165 A wrapper around a `const(char)*` to indicate that it is a zero-terminated C string. 166 +/ 167 struct stringz { 168 private const(char)* raw; 169 170 /++ 171 Wraps the given pointer in the struct. Note that it retains a copy of the pointer. 172 +/ 173 this(const(char)* raw) { 174 this.raw = raw; 175 } 176 177 /++ 178 Returns the original raw pointer back out. 179 +/ 180 const(char)* ptr() const { 181 return raw; 182 } 183 184 /++ 185 Borrows a slice of the pointer up to (but not including) the zero terminator. 186 +/ 187 const(char)[] borrow() const { 188 if(raw is null) 189 return null; 190 191 const(char)* p = raw; 192 int length; 193 while(*p++) length++; 194 195 return raw[0 .. length]; 196 } 197 } 198 199 /++ 200 A limited variant to hold just a few types. It is made for the use of packing a small amount of extra data into error messages. 201 +/ 202 /+ 203 * if length and ptr are both 0, it is null 204 * if ptr == 1, length is an integer 205 * if ptr == 2, length is an unsigned integer (suggest printing in hex) 206 * if ptr == 3, length is a combination of flags (suggest printing in binary) 207 * if ptr == 4, length is a unix permission thing (suggest printing in octal) 208 * if ptr == 5, length is a double float 209 * if ptr == 15, length must be 0. this holds an empty, non-null, SSO string. 210 * if ptr >= 16 && < 24, length is reinterpret-casted a small string of length of (ptr & 0x7) + 1 211 * if length == size_t.max, ptr is interpreted as a stringz 212 * if ptr >= 1024, it is a non-null D string or byte array. It is a string if the length high bit is clear, a byte array if it is set. the length is what is left after you mask that out. 213 214 All other ptr values are reserved for future expansion. 215 +/ 216 struct LimitedVariant { 217 218 /++ 219 220 +/ 221 enum Contains { 222 null_, 223 intDecimal, 224 intHex, 225 intBinary, 226 intOctal, 227 double_, 228 emptySso, 229 stringSso, 230 stringz, 231 string, 232 bytes, 233 234 invalid, 235 } 236 237 /++ 238 239 +/ 240 Contains contains() const { 241 auto tag = cast(size_t) ptr; 242 if(ptr is null && length is null) 243 return Contains.null_; 244 else switch(tag) { 245 case 1: return Contains.intDecimal; 246 case 2: return Contains.intHex; 247 case 3: return Contains.intBinary; 248 case 4: return Contains.intOctal; 249 case 5: return Contains.double_; 250 case 15: return length is null ? Contains.emptySso : Contains.invalid; 251 default: 252 if(tag >= 16 && tag < 24) { 253 return Contains.stringSso; 254 } else if(tag >= 1024) { 255 if(cast(size_t) length == size_t.max) 256 return Contains.stringz; 257 else 258 return isHighBitSet ? Contains.bytes : Contains..string; 259 } else { 260 return Contains.invalid; 261 } 262 } 263 } 264 265 /// ditto 266 bool containsInt() const { 267 with(Contains) 268 switch(contains) { 269 case intDecimal, intHex, intBinary, intOctal: 270 return true; 271 default: 272 return false; 273 } 274 } 275 276 /// ditto 277 bool containsString() const { 278 with(Contains) 279 switch(contains) { 280 case null_, emptySso, stringSso, string: 281 // case stringz: 282 return true; 283 default: 284 return false; 285 } 286 } 287 288 /// ditto 289 bool containsDouble() const { 290 with(Contains) 291 switch(contains) { 292 case double_: 293 return true; 294 default: 295 return false; 296 } 297 } 298 299 /// ditto 300 bool containsBytes() const { 301 with(Contains) 302 switch(contains) { 303 case bytes, null_: 304 return true; 305 default: 306 return false; 307 } 308 } 309 310 private const(void)* length; 311 private const(ubyte)* ptr; 312 313 private void Throw() const { 314 throw ArsdException!"LimitedVariant"(cast(size_t) length, cast(size_t) ptr); 315 } 316 317 private bool isHighBitSet() const { 318 return (cast(size_t) length >> (size_t.sizeof * 8 - 1) & 0x1) != 0; 319 } 320 321 /++ 322 getString gets a reference to the string stored internally, see [toString] to get a string representation or whatever is inside. 323 324 +/ 325 const(char)[] getString() const return { 326 with(Contains) 327 switch(contains()) { 328 case null_: 329 return null; 330 case emptySso: 331 return (cast(const(char)*) ptr)[0 .. 0]; // zero length, non-null 332 case stringSso: 333 auto len = ((cast(size_t) ptr) & 0x7) + 1; 334 return (cast(char*) &length)[0 .. len]; 335 case string: 336 return (cast(const(char)*) ptr)[0 .. cast(size_t) length]; 337 default: 338 Throw(); assert(0); 339 } 340 } 341 342 /// ditto 343 long getInt() const { 344 if(containsInt) 345 return cast(long) length; 346 else 347 Throw(); 348 assert(0); 349 } 350 351 /// ditto 352 double getDouble() const { 353 if(containsDouble) 354 return *cast(double*) &length; 355 else 356 Throw(); 357 assert(0); 358 } 359 360 /// ditto 361 const(ubyte)[] getBytes() const { 362 with(Contains) 363 switch(contains()) { 364 case null_: 365 return null; 366 case bytes: 367 return ptr[0 .. (cast(size_t) length) & ((1UL << (size_t.sizeof * 8 - 1)) - 1)]; 368 default: 369 Throw(); assert(0); 370 } 371 } 372 373 /++ 374 375 +/ 376 string toString() const { 377 378 string intHelper(string prefix, int radix) { 379 char[128] buffer; 380 buffer[0 .. prefix.length] = prefix[]; 381 char[] toUse = buffer[prefix.length .. $]; 382 383 auto got = intToString(getInt(), toUse[], IntToStringArgs().withRadix(radix)); 384 385 return buffer[0 .. prefix.length + got.length].idup; 386 } 387 388 with(Contains) 389 final switch(contains()) { 390 case null_: 391 return "<null>"; 392 case intDecimal: 393 return intHelper("", 10); 394 case intHex: 395 return intHelper("0x", 16); 396 case intBinary: 397 return intHelper("0b", 2); 398 case intOctal: 399 return intHelper("0o", 8); 400 case emptySso, stringSso, string: 401 return getString().idup; 402 case bytes: 403 auto b = getBytes(); 404 405 return "<bytes>"; // FIXME 406 407 case double_: 408 assert(0); // FIXME 409 case stringz: 410 assert(0); // FIXME 411 case invalid: 412 return "<invalid>"; 413 } 414 } 415 416 /++ 417 418 +/ 419 this(string s) { 420 ptr = cast(const(ubyte)*) s.ptr; 421 length = cast(void*) s.length; 422 } 423 424 /// ditto 425 this(const(ubyte)[] b) { 426 ptr = cast(const(ubyte)*) b.ptr; 427 length = cast(void*) (b.length | (1UL << (size_t.sizeof * 8 - 1))); 428 } 429 430 /// ditto 431 this(long l, int base = 10) { 432 int tag; 433 switch(base) { 434 case 10: tag = 1; break; 435 case 16: tag = 2; break; 436 case 2: tag = 3; break; 437 case 8: tag = 4; break; 438 default: assert(0, "You passed an invalid base to LimitedVariant"); 439 } 440 ptr = cast(ubyte*) tag; 441 length = cast(void*) l; 442 } 443 444 /// ditto 445 version(none) 446 this(double d) { 447 // this crashes dmd! omg 448 assert(0); 449 // ptr = cast(ubyte*) 15; 450 // length = cast(void*) *cast(size_t*) &d; 451 } 452 } 453 454 unittest { 455 LimitedVariant v = LimitedVariant("foo"); 456 assert(v.containsString()); 457 assert(!v.containsInt()); 458 assert(v.getString() == "foo"); 459 460 LimitedVariant v2 = LimitedVariant(4); 461 assert(v2.containsInt()); 462 assert(!v2.containsString()); 463 assert(v2.getInt() == 4); 464 465 LimitedVariant v3 = LimitedVariant(cast(ubyte[]) [1, 2, 3]); 466 assert(v3.containsBytes()); 467 assert(!v3.containsString()); 468 assert(v3.getBytes() == [1, 2, 3]); 469 } 470 471 /++ 472 This is a dummy type to indicate the end of normal arguments and the beginning of the file/line inferred args. It is meant to ensure you don't accidentally send a string that is interpreted as a filename when it was meant to be a normal argument to the function and trigger the wrong overload. 473 +/ 474 struct ArgSentinel {} 475 476 /++ 477 A trivial wrapper around C's malloc that creates a D slice. It multiples n by T.sizeof and returns the slice of the pointer from 0 to n. 478 479 Please note that the ptr might be null - it is your responsibility to check that, same as normal malloc. Check `ret is null` specifically, since `ret.length` will always be `n`, even if the `malloc` failed. 480 481 Remember to `free` the returned pointer with `core.stdc.stdlib.free(ret.ptr);` 482 483 $(TIP 484 I strongly recommend you simply use the normal garbage collector unless you have a very specific reason not to. 485 ) 486 487 See_Also: 488 [mallocedStringz] 489 +/ 490 T[] mallocSlice(T)(size_t n) { 491 import c = core.stdc.stdlib; 492 493 return (cast(T*) c.malloc(n * T.sizeof))[0 .. n]; 494 } 495 496 /++ 497 Uses C's malloc to allocate a copy of `original` with an attached zero terminator. It may return a slice with a `null` pointer (but non-zero length!) if `malloc` fails and you are responsible for freeing the returned pointer with `core.stdc.stdlib.free(ret.ptr)`. 498 499 $(TIP 500 I strongly recommend you use [CharzBuffer] or Phobos' [std.string.toStringz] instead unless there's a special reason not to. 501 ) 502 503 See_Also: 504 [CharzBuffer] for a generally better alternative. You should only use `mallocedStringz` where `CharzBuffer` cannot be used (e.g. when druntime is not usable or you have no stack space for the temporary buffer). 505 506 [mallocSlice] is the function this function calls, so the notes in its documentation applies here too. 507 +/ 508 char[] mallocedStringz(in char[] original) { 509 auto slice = mallocSlice!char(original.length + 1); 510 if(slice is null) 511 return null; 512 slice[0 .. original.length] = original[]; 513 slice[original.length] = 0; 514 return slice; 515 } 516 517 /++ 518 Basically a `scope class` you can return from a function or embed in another aggregate. 519 +/ 520 struct OwnedClass(Class) { 521 ubyte[__traits(classInstanceSize, Class)] rawData; 522 523 static OwnedClass!Class defaultConstructed() { 524 OwnedClass!Class i = OwnedClass!Class.init; 525 i.initializeRawData(); 526 return i; 527 } 528 529 private void initializeRawData() @trusted { 530 if(!this) 531 rawData[] = cast(ubyte[]) typeid(Class).initializer[]; 532 } 533 534 this(T...)(T t) { 535 initializeRawData(); 536 rawInstance.__ctor(t); 537 } 538 539 bool opCast(T : bool)() @trusted { 540 return !(*(cast(void**) rawData.ptr) is null); 541 } 542 543 @disable this(); 544 @disable this(this); 545 546 Class rawInstance() return @trusted { 547 if(!this) 548 throw new Exception("null"); 549 return cast(Class) rawData.ptr; 550 } 551 552 alias rawInstance this; 553 554 ~this() @trusted { 555 if(this) 556 .destroy(rawInstance()); 557 } 558 } 559 560 // might move RecyclableMemory here 561 562 version(Posix) 563 package(arsd) void makeNonBlocking(int fd) { 564 import core.sys.posix.fcntl; 565 auto flags = fcntl(fd, F_GETFL, 0); 566 if(flags == -1) 567 throw new ErrnoApiException("fcntl get", errno); 568 flags |= O_NONBLOCK; 569 auto s = fcntl(fd, F_SETFL, flags); 570 if(s == -1) 571 throw new ErrnoApiException("fcntl set", errno); 572 } 573 574 version(Posix) 575 package(arsd) void setCloExec(int fd) { 576 import core.sys.posix.fcntl; 577 auto flags = fcntl(fd, F_GETFD, 0); 578 if(flags == -1) 579 throw new ErrnoApiException("fcntl get", errno); 580 flags |= FD_CLOEXEC; 581 auto s = fcntl(fd, F_SETFD, flags); 582 if(s == -1) 583 throw new ErrnoApiException("fcntl set", errno); 584 } 585 586 587 /++ 588 A helper object for temporarily constructing a string appropriate for the Windows API from a D UTF-8 string. 589 590 591 It will use a small internal static buffer is possible, and allocate a new buffer if the string is too big. 592 593 History: 594 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 595 +/ 596 version(Windows) 597 struct WCharzBuffer { 598 private wchar[] buffer; 599 private wchar[128] staticBuffer = void; 600 601 /// Length of the string, excluding the zero terminator. 602 size_t length() { 603 return buffer.length; 604 } 605 606 // Returns the pointer to the internal buffer. You must assume its lifetime is less than that of the WCharzBuffer. It is zero-terminated. 607 wchar* ptr() { 608 return buffer.ptr; 609 } 610 611 /// Returns the slice of the internal buffer, excluding the zero terminator (though there is one present right off the end of the slice). You must assume its lifetime is less than that of the WCharzBuffer. 612 wchar[] slice() { 613 return buffer; 614 } 615 616 /// Copies it into a static array of wchars 617 void copyInto(R)(ref R r) { 618 static if(is(R == wchar[N], size_t N)) { 619 r[0 .. this.length] = slice[]; 620 r[this.length] = 0; 621 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 622 } 623 624 /++ 625 conversionFlags = [WindowsStringConversionFlags] 626 +/ 627 this(in char[] data, int conversionFlags = 0) { 628 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 629 auto sz = sizeOfConvertedWstring(data, conversionFlags); 630 if(sz > staticBuffer.length) 631 buffer = new wchar[](sz); 632 else 633 buffer = staticBuffer[]; 634 635 buffer = makeWindowsString(data, buffer, conversionFlags); 636 } 637 } 638 639 /++ 640 Alternative for toStringz 641 642 History: 643 Added March 18, 2023 (dub v11.0) 644 +/ 645 struct CharzBuffer { 646 private char[] buffer; 647 private char[128] staticBuffer = void; 648 649 /// Length of the string, excluding the zero terminator. 650 size_t length() { 651 assert(buffer.length > 0); 652 return buffer.length - 1; 653 } 654 655 // Returns the pointer to the internal buffer. You must assume its lifetime is less than that of the CharzBuffer. It is zero-terminated. 656 char* ptr() { 657 return buffer.ptr; 658 } 659 660 /// Returns the slice of the internal buffer, excluding the zero terminator (though there is one present right off the end of the slice). You must assume its lifetime is less than that of the CharzBuffer. 661 char[] slice() { 662 assert(buffer.length > 0); 663 return buffer[0 .. $-1]; 664 } 665 666 /// Copies it into a static array of chars 667 void copyInto(R)(ref R r) { 668 static if(is(R == char[N], size_t N)) { 669 r[0 .. this.length] = slice[]; 670 r[this.length] = 0; 671 } else static assert(0, "can only copy into char[n], not " ~ R.stringof); 672 } 673 674 @disable this(); 675 @disable this(this); 676 677 /++ 678 Copies `data` into the CharzBuffer, allocating a new one if needed, and zero-terminates it. 679 +/ 680 this(in char[] data) { 681 if(data.length + 1 > staticBuffer.length) 682 buffer = new char[](data.length + 1); 683 else 684 buffer = staticBuffer[]; 685 686 buffer[0 .. data.length] = data[]; 687 buffer[data.length] = 0; 688 } 689 } 690 691 /++ 692 Given the string `str`, converts it to a string compatible with the Windows API and puts the result in `buffer`, returning the slice of `buffer` actually used. `buffer` must be at least [sizeOfConvertedWstring] elements long. 693 694 History: 695 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 696 +/ 697 version(Windows) 698 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 699 if(str.length == 0) 700 return null; 701 702 int pos = 0; 703 dchar last; 704 foreach(dchar c; str) { 705 if(c <= 0xFFFF) { 706 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 707 buffer[pos++] = 13; 708 buffer[pos++] = cast(wchar) c; 709 } else if(c <= 0x10FFFF) { 710 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 711 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 712 } 713 714 last = c; 715 } 716 717 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 718 buffer[pos] = 0; 719 } 720 721 return buffer[0 .. pos]; 722 } 723 724 /++ 725 Converts the Windows API string `str` to a D UTF-8 string, storing it in `buffer`. Returns the slice of `buffer` actually used. 726 727 History: 728 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 729 +/ 730 version(Windows) 731 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 732 if(str.length == 0) 733 return null; 734 735 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 736 if(got == 0) { 737 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 738 throw new object.Exception("not enough buffer"); 739 else 740 throw new object.Exception("conversion"); // FIXME: GetLastError 741 } 742 return buffer[0 .. got]; 743 } 744 745 /++ 746 Converts the Windows API string `str` to a newly-allocated D UTF-8 string. 747 748 History: 749 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 750 +/ 751 version(Windows) 752 string makeUtf8StringFromWindowsString(in wchar[] str) { 753 char[] buffer; 754 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 755 buffer.length = got; 756 757 // it is unique because we just allocated it above! 758 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 759 } 760 761 /// ditto 762 version(Windows) 763 string makeUtf8StringFromWindowsString(wchar* str) { 764 char[] buffer; 765 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 766 buffer.length = got; 767 768 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 769 if(got == 0) { 770 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 771 throw new object.Exception("not enough buffer"); 772 else 773 throw new object.Exception("conversion"); // FIXME: GetLastError 774 } 775 return cast(string) buffer[0 .. got]; 776 } 777 778 // only used from minigui rn 779 package int findIndexOfZero(in wchar[] str) { 780 foreach(idx, wchar ch; str) 781 if(ch == 0) 782 return cast(int) idx; 783 return cast(int) str.length; 784 } 785 package int findIndexOfZero(in char[] str) { 786 foreach(idx, char ch; str) 787 if(ch == 0) 788 return cast(int) idx; 789 return cast(int) str.length; 790 } 791 792 /++ 793 Returns a minimum buffer length to hold the string `s` with the given conversions. It might be slightly larger than necessary, but is guaranteed to be big enough to hold it. 794 795 History: 796 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 797 +/ 798 version(Windows) 799 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 800 int size = 0; 801 802 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 803 // need to convert line endings, which means the length will get bigger. 804 805 // BTW I betcha this could be faster with some simd stuff. 806 char last; 807 foreach(char ch; s) { 808 if(ch == 10 && last != 13) 809 size++; // will add a 13 before it... 810 size++; 811 last = ch; 812 } 813 } else { 814 // no conversion necessary, just estimate based on length 815 /* 816 I don't think there's any string with a longer length 817 in code units when encoded in UTF-16 than it has in UTF-8. 818 This will probably over allocate, but that's OK. 819 */ 820 size = cast(int) s.length; 821 } 822 823 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 824 size++; 825 826 return size; 827 } 828 829 /++ 830 Used by [makeWindowsString] and [WCharzBuffer] 831 832 History: 833 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 834 +/ 835 version(Windows) 836 enum WindowsStringConversionFlags : int { 837 /++ 838 Append a zero terminator to the string. 839 +/ 840 zeroTerminate = 1, 841 /++ 842 Converts newlines from \n to \r\n. 843 +/ 844 convertNewLines = 2, 845 } 846 847 /++ 848 An int printing function that doesn't need to import Phobos. Can do some of the things std.conv.to and std.format.format do. 849 850 The buffer must be sized to hold the converted number. 32 chars is enough for most anything. 851 852 Returns: the slice of `buffer` containing the converted number. 853 +/ 854 char[] intToString(long value, char[] buffer, IntToStringArgs args = IntToStringArgs.init) { 855 const int radix = args.radix ? args.radix : 10; 856 const int digitsPad = args.padTo; 857 const int groupSize = args.groupSize; 858 859 int pos; 860 861 if(value < 0) { 862 buffer[pos++] = '-'; 863 value = -value; 864 } 865 866 int start = pos; 867 int digitCount; 868 869 do { 870 auto remainder = value % radix; 871 value = value / radix; 872 873 buffer[pos++] = cast(char) (remainder < 10 ? (remainder + '0') : (remainder - 10 + args.ten)); 874 digitCount++; 875 } while(value); 876 877 if(digitsPad > 0) { 878 while(digitCount < digitsPad) { 879 buffer[pos++] = args.padWith; 880 digitCount++; 881 } 882 } 883 884 assert(pos >= 1); 885 assert(pos - start > 0); 886 887 auto reverseSlice = buffer[start .. pos]; 888 for(int i = 0; i < reverseSlice.length / 2; i++) { 889 auto paired = cast(int) reverseSlice.length - i - 1; 890 char tmp = reverseSlice[i]; 891 reverseSlice[i] = reverseSlice[paired]; 892 reverseSlice[paired] = tmp; 893 } 894 895 return buffer[0 .. pos]; 896 } 897 898 /// ditto 899 struct IntToStringArgs { 900 private { 901 ubyte padTo; 902 char padWith; 903 ubyte radix; 904 char ten; 905 ubyte groupSize; 906 char separator; 907 } 908 909 IntToStringArgs withPadding(int padTo, char padWith = '0') { 910 IntToStringArgs args = this; 911 args.padTo = cast(ubyte) padTo; 912 args.padWith = padWith; 913 return args; 914 } 915 916 IntToStringArgs withRadix(int radix, char ten = 'a') { 917 IntToStringArgs args = this; 918 args.radix = cast(ubyte) radix; 919 args.ten = ten; 920 return args; 921 } 922 923 IntToStringArgs withGroupSeparator(int groupSize, char separator = '_') { 924 IntToStringArgs args = this; 925 args.groupSize = cast(ubyte) groupSize; 926 args.separator = separator; 927 return args; 928 } 929 } 930 931 unittest { 932 char[32] buffer; 933 assert(intToString(0, buffer[]) == "0"); 934 assert(intToString(-1, buffer[]) == "-1"); 935 assert(intToString(-132, buffer[]) == "-132"); 936 assert(intToString(-1932, buffer[]) == "-1932"); 937 assert(intToString(1, buffer[]) == "1"); 938 assert(intToString(132, buffer[]) == "132"); 939 assert(intToString(1932, buffer[]) == "1932"); 940 941 assert(intToString(0x1, buffer[], IntToStringArgs().withRadix(16)) == "1"); 942 assert(intToString(0x1b, buffer[], IntToStringArgs().withRadix(16)) == "1b"); 943 assert(intToString(0xef1, buffer[], IntToStringArgs().withRadix(16)) == "ef1"); 944 945 assert(intToString(0xef1, buffer[], IntToStringArgs().withRadix(16).withPadding(8)) == "00000ef1"); 946 assert(intToString(-0xef1, buffer[], IntToStringArgs().withRadix(16).withPadding(8)) == "-00000ef1"); 947 assert(intToString(-0xef1, buffer[], IntToStringArgs().withRadix(16, 'A').withPadding(8, ' ')) == "- EF1"); 948 } 949 950 /++ 951 History: 952 Moved from color.d to core.d in March 2023 (dub v11.0). 953 +/ 954 nothrow @safe @nogc pure 955 inout(char)[] stripInternal(return inout(char)[] s) { 956 bool isAllWhitespace = true; 957 foreach(i, char c; s) 958 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 959 s = s[i .. $]; 960 isAllWhitespace = false; 961 break; 962 } 963 964 if(isAllWhitespace) 965 return s[$..$]; 966 967 for(int a = cast(int)(s.length - 1); a > 0; a--) { 968 char c = s[a]; 969 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 970 s = s[0 .. a + 1]; 971 break; 972 } 973 } 974 975 return s; 976 } 977 978 /// ditto 979 nothrow @safe @nogc pure 980 inout(char)[] stripRightInternal(return inout(char)[] s) { 981 bool isAllWhitespace = true; 982 foreach_reverse(a, c; s) { 983 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 984 s = s[0 .. a + 1]; 985 isAllWhitespace = false; 986 break; 987 } 988 } 989 if(isAllWhitespace) 990 s = s[0..0]; 991 992 return s; 993 994 } 995 996 /++ 997 Shortcut for converting some types to string without invoking Phobos (but it will as a last resort). 998 999 History: 1000 Moved from color.d to core.d in March 2023 (dub v11.0). 1001 +/ 1002 string toStringInternal(T)(T t) { 1003 char[32] buffer; 1004 static if(is(T : string)) 1005 return t; 1006 else static if(is(T : long)) 1007 return intToString(t, buffer[]).idup; 1008 else static if(is(T == enum)) { 1009 switch(t) { 1010 foreach(memberName; __traits(allMembers, T)) { 1011 case __traits(getMember, T, memberName): 1012 return memberName; 1013 } 1014 default: 1015 return "<unknown>"; 1016 } 1017 } else { 1018 import std.conv; 1019 return to!string(t); 1020 } 1021 } 1022 1023 /++ 1024 1025 +/ 1026 string flagsToString(Flags)(ulong value) { 1027 string r; 1028 1029 void add(string memberName) { 1030 if(r.length) 1031 r ~= " | "; 1032 r ~= memberName; 1033 } 1034 1035 string none = "<none>"; 1036 1037 foreach(memberName; __traits(allMembers, Flags)) { 1038 auto flag = cast(ulong) __traits(getMember, Flags, memberName); 1039 if(flag) { 1040 if((value & flag) == flag) 1041 add(memberName); 1042 } else { 1043 none = memberName; 1044 } 1045 } 1046 1047 if(r.length == 0) 1048 r = none; 1049 1050 return r; 1051 } 1052 1053 unittest { 1054 enum MyFlags { 1055 none = 0, 1056 a = 1, 1057 b = 2 1058 } 1059 1060 assert(flagsToString!MyFlags(3) == "a | b"); 1061 assert(flagsToString!MyFlags(0) == "none"); 1062 assert(flagsToString!MyFlags(2) == "b"); 1063 } 1064 1065 private auto toDelegate(T)(T t) { 1066 // static assert(is(T == function)); // lol idk how to do what i actually want here 1067 1068 static if(is(T Return == return)) 1069 static if(is(typeof(*T) Params == __parameters)) { 1070 static struct Wrapper { 1071 Return call(Params params) { 1072 return (cast(T) &this)(params); 1073 } 1074 } 1075 return &((cast(Wrapper*) t).call); 1076 } else static assert(0, "could not get params"); 1077 else static assert(0, "could not get return value"); 1078 } 1079 1080 unittest { 1081 int function(int) fn; 1082 fn = (a) { return a; }; 1083 1084 int delegate(int) dg = toDelegate(fn); 1085 1086 assert(dg.ptr is fn); // it stores the original function as the context pointer 1087 assert(dg.funcptr !is fn); // which is called through a lil trampoline 1088 assert(dg(5) == 5); // and forwards the args correctly 1089 } 1090 1091 /++ 1092 This populates a struct from a list of values (or other expressions, but it only looks at the values) based on types of the members, with one exception: `bool` members.. maybe. 1093 1094 It is intended for collecting a record of relevant UDAs off a symbol in a single call like this: 1095 1096 --- 1097 struct Name { 1098 string n; 1099 } 1100 1101 struct Validator { 1102 string regex; 1103 } 1104 1105 struct FormInfo { 1106 Name name; 1107 Validator validator; 1108 } 1109 1110 @Name("foo") @Validator(".*") 1111 void foo() {} 1112 1113 auto info = populateFromUdas!(FormInfo, __traits(getAttributes, foo)); 1114 assert(info.name == Name("foo")); 1115 assert(info.validator == Validator(".*")); 1116 --- 1117 1118 Note that instead of UDAs, you can also pass a variadic argument list and get the same result, but the function is `populateFromArgs` and you pass them as the runtime list to bypass "args cannot be evaluated at compile time" errors: 1119 1120 --- 1121 void foo(T...)(T t) { 1122 auto info = populateFromArgs!(FormInfo)(t); 1123 // assuming the call below 1124 assert(info.name == Name("foo")); 1125 assert(info.validator == Validator(".*")); 1126 } 1127 1128 foo(Name("foo"), Validator(".*")); 1129 --- 1130 1131 The benefit of this over constructing the struct directly is that the arguments can be reordered or missing. Its value is diminished with named arguments in the language. 1132 +/ 1133 template populateFromUdas(Struct, UDAs...) { 1134 enum Struct populateFromUdas = () { 1135 Struct ret; 1136 foreach(memberName; __traits(allMembers, Struct)) { 1137 alias memberType = typeof(__traits(getMember, Struct, memberName)); 1138 foreach(uda; UDAs) { 1139 static if(is(memberType == PresenceOf!a, a)) { 1140 static if(__traits(isSame, a, uda)) 1141 __traits(getMember, ret, memberName) = true; 1142 } 1143 else 1144 static if(is(typeof(uda) : memberType)) { 1145 __traits(getMember, ret, memberName) = uda; 1146 } 1147 } 1148 } 1149 1150 return ret; 1151 }(); 1152 } 1153 1154 /// ditto 1155 Struct populateFromArgs(Struct, Args...)(Args args) { 1156 Struct ret; 1157 foreach(memberName; __traits(allMembers, Struct)) { 1158 alias memberType = typeof(__traits(getMember, Struct, memberName)); 1159 foreach(arg; args) { 1160 static if(is(typeof(arg == memberType))) { 1161 __traits(getMember, ret, memberName) = arg; 1162 } 1163 } 1164 } 1165 1166 return ret; 1167 } 1168 1169 /// ditto 1170 struct PresenceOf(alias a) { 1171 bool there; 1172 alias there this; 1173 } 1174 1175 /// 1176 unittest { 1177 enum a; 1178 enum b; 1179 struct Name { string name; } 1180 struct Info { 1181 Name n; 1182 PresenceOf!a athere; 1183 PresenceOf!b bthere; 1184 int c; 1185 } 1186 1187 void test() @a @Name("test") {} 1188 1189 auto info = populateFromUdas!(Info, __traits(getAttributes, test)); 1190 assert(info.n == Name("test")); // but present ones are in there 1191 assert(info.athere == true); // non-values can be tested with PresenceOf!it, which works like a bool 1192 assert(info.bthere == false); 1193 assert(info.c == 0); // absent thing will keep the default value 1194 } 1195 1196 /++ 1197 Declares a delegate property with several setters to allow for handlers that don't care about the arguments. 1198 1199 Throughout the arsd library, you will often see types of these to indicate that you can set listeners with or without arguments. If you care about the details of the callback event, you can set a delegate that declares them. And if you don't, you can set one that doesn't even declare them and it will be ignored. 1200 +/ 1201 struct FlexibleDelegate(DelegateType) { 1202 // please note that Parameters and ReturnType are public now! 1203 static if(is(DelegateType FunctionType == delegate)) 1204 static if(is(FunctionType Parameters == __parameters)) 1205 static if(is(DelegateType ReturnType == return)) { 1206 1207 /++ 1208 Calls the currently set delegate. 1209 1210 Diagnostics: 1211 If the callback delegate has not been set, this may cause a null pointer dereference. 1212 +/ 1213 ReturnType opCall(Parameters args) { 1214 return dg(args); 1215 } 1216 1217 /++ 1218 Use `if(thing)` to check if the delegate is null or not. 1219 +/ 1220 bool opCast(T : bool)() { 1221 return dg !is null; 1222 } 1223 1224 /++ 1225 These opAssign overloads are what puts the flexibility in the flexible delegate. 1226 1227 Bugs: 1228 The other overloads do not keep attributes like `nothrow` on the `dg` parameter, making them unusable if `DelegateType` requires them. I consider the attributes more trouble than they're worth anyway, and the language's poor support for composing them doesn't help any. I have no need for them and thus no plans to add them in the overloads at this time. 1229 +/ 1230 void opAssign(DelegateType dg) { 1231 this.dg = dg; 1232 } 1233 1234 /// ditto 1235 void opAssign(ReturnType delegate() dg) { 1236 this.dg = (Parameters ignored) => dg(); 1237 } 1238 1239 /// ditto 1240 void opAssign(ReturnType function(Parameters params) dg) { 1241 this.dg = (Parameters params) => dg(params); 1242 } 1243 1244 /// ditto 1245 void opAssign(ReturnType function() dg) { 1246 this.dg = (Parameters ignored) => dg(); 1247 } 1248 1249 /// ditto 1250 void opAssign(typeof(null) explicitNull) { 1251 this.dg = null; 1252 } 1253 1254 private DelegateType dg; 1255 } 1256 else static assert(0, DelegateType.stringof ~ " failed return value check"); 1257 else static assert(0, DelegateType.stringof ~ " failed parameters check"); 1258 else static assert(0, DelegateType.stringof ~ " failed delegate check"); 1259 } 1260 1261 /++ 1262 1263 +/ 1264 unittest { 1265 // you don't have to put the arguments in a struct, but i recommend 1266 // you do as it is more future proof - you can add more info to the 1267 // struct without breaking user code that consumes it. 1268 struct MyEventArguments { 1269 1270 } 1271 1272 // then you declare it just adding FlexibleDelegate!() around the 1273 // plain delegate type you'd normally use 1274 FlexibleDelegate!(void delegate(MyEventArguments args)) callback; 1275 1276 // until you set it, it will be null and thus be false in any boolean check 1277 assert(!callback); 1278 1279 // can set it to the properly typed thing 1280 callback = delegate(MyEventArguments args) {}; 1281 1282 // and now it is no longer null 1283 assert(callback); 1284 1285 // or if you don't care about the args, you can leave them off 1286 callback = () {}; 1287 1288 // and it works if the compiler types you as a function instead of delegate too 1289 // (which happens automatically if you don't access any local state or if you 1290 // explicitly define it as a function) 1291 1292 callback = function(MyEventArguments args) { }; 1293 1294 // can set it back to null explicitly if you ever wanted 1295 callback = null; 1296 1297 // the reflection info used internally also happens to be exposed publicly 1298 // which can actually sometimes be nice so if the language changes, i'll change 1299 // the code to keep this working. 1300 static assert(is(callback.ReturnType == void)); 1301 1302 // which can be convenient if the params is an annoying type since you can 1303 // consistently use something like this too 1304 callback = (callback.Parameters params) {}; 1305 1306 // check for null and call it pretty normally 1307 if(callback) 1308 callback(MyEventArguments()); 1309 } 1310 1311 /+ 1312 ====================== 1313 ERROR HANDLING HELPERS 1314 ====================== 1315 +/ 1316 1317 /+ + 1318 arsd code shouldn't be using Exception. Really, I don't think any code should be - instead, construct an appropriate object with structured information. 1319 1320 If you want to catch someone else's Exception, use `catch(object.Exception e)`. 1321 +/ 1322 //package deprecated struct Exception {} 1323 1324 1325 /++ 1326 Base class representing my exceptions. You should almost never work with this directly, but you might catch it as a generic thing. Catch it before generic `object.Exception` or `object.Throwable` in any catch chains. 1327 1328 1329 $(H3 General guidelines for exceptions) 1330 1331 The purpose of an exception is to cancel a task that has proven to be impossible and give the programmer enough information to use at a higher level to decide what to do about it. 1332 1333 Cancelling a task is accomplished with the `throw` keyword. The transmission of information to a higher level is done by the language runtime. The decision point is marked by the `catch` keyword. The part missing - the job of the `Exception` class you construct and throw - is to gather the information that will be useful at a later decision point. 1334 1335 It is thus important that you gather as much useful information as possible and keep it in a way that the code catching the exception can still interpret it when constructing an exception. Other concerns are secondary to this to this primary goal. 1336 1337 With this in mind, here's some guidelines for exception handling in arsd code. 1338 1339 $(H4 Allocations and lifetimes) 1340 1341 Don't get clever with exception allocations. You don't know what the catcher is going to do with an exception and you don't want the error handling scheme to introduce its own tricky bugs. Remember, an exception object's first job is to deliver useful information up the call chain in a way this code can use it. You don't know what this code is or what it is going to do. 1342 1343 Keep your memory management schemes simple and let the garbage collector do its job. 1344 1345 $(LIST 1346 * All thrown exceptions should be allocated with the `new` keyword. 1347 1348 * Members inside the exception should be value types or have infinite lifetime (that is, be GC managed). 1349 1350 * While this document is concerned with throwing, you might want to add additional information to an in-flight exception, and this is done by catching, so you need to know how that works too, and there is a global compiler switch that can change things, so even inside arsd we can't completely avoid its implications. 1351 1352 DIP1008's presence complicates things a bit on the catch side - if you catch an exception and return it from a function, remember to `ex.refcount = ex.refcount + 1;` so you don't introduce more use-after-free woes for those unfortunate souls. 1353 ) 1354 1355 $(H4 Error strings) 1356 1357 Strings can deliver useful information to people reading the message, but are often suboptimal for delivering useful information to other chunks of code. Remember, an exception's first job is to be caught by another block of code. Printing to users is a last resort; even if you want a user-readable error message, an exception is not the ideal way to deliver one since it is constructed in the guts of a failed task, without the higher level context of what the user was actually trying to do. User error messages ought to be made from information in the exception, combined with higher level knowledge. This is best done in a `catch` block, not a `throw` statement. 1358 1359 As such, I recommend that you: 1360 1361 $(LIST 1362 * Don't concatenate error strings at the throw site. Instead, pass the data you would have used to build the string as actual data to the constructor. This lets catchers see the original data without having to try to extract it from a string. For unique data, you will likely need a unique exception type. More on this in the next section. 1363 1364 * Don't construct error strings in a constructor either, for the same reason. Pass the useful data up the call chain, as exception members, to the maximum extent possible. Exception: if you are passed some data with a temporary lifetime that is important enough to pass up the chain. You may `.idup` or `to!string` to preserve as much data as you can before it is lost, but still store it in a separate member of the Exception subclass object. 1365 1366 * $(I Do) construct strings out of public members in [getAdditionalPrintableInformation]. When this is called, the user has requested as much relevant information as reasonable in string format. Still, avoid concatenation - it lets you pass as many key/value pairs as you like to the caller. They can concatenate as needed. However, note the words "public members" - everything you do in `getAdditionalPrintableInformation` ought to also be possible for code that caught your exception via your public methods and properties. 1367 ) 1368 1369 $(H4 Subclasses) 1370 1371 Any exception with unique data types should be a unique class. Whenever practical, this should be one you write and document at the top-level of a module. But I know we get lazy - me too - and this is why in standard D we'd often fall back to `throw new Exception("some string " ~ some info)`. To help resist these urges, I offer some helper functions to use instead that better achieve the key goal of exceptions - passing structured data up a call chain - while still being convenient to write. 1372 1373 See: [ArsdException], [Win32Enforce] 1374 1375 +/ 1376 class ArsdExceptionBase : object.Exception { 1377 /++ 1378 Don't call this except from other exceptions; this is essentially an abstract class. 1379 1380 Params: 1381 operation = the specific operation that failed, throwing the exception 1382 +/ 1383 package this(string operation, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1384 super(operation, file, line, next); 1385 } 1386 1387 /++ 1388 The toString method will print out several components: 1389 1390 $(LIST 1391 * The file, line, static message, and object class name from the constructor. You can access these independently with the members `file`, `line`, `msg`, and [printableExceptionName]. 1392 * The generic category codes stored with this exception 1393 * Additional members stored with the exception child classes (e.g. platform error codes, associated function arguments) 1394 * The stack trace associated with the exception. You can access these lines independently with `foreach` over the `info` member. 1395 ) 1396 1397 This is meant to be read by the developer, not end users. You should wrap your user-relevant tasks in a try/catch block and construct more appropriate error messages from context available there, using the individual properties of the exception to add richness. 1398 +/ 1399 final override void toString(scope void delegate(in char[]) sink) const { 1400 // class name and info from constructor 1401 sink(printableExceptionName); 1402 sink("@"); 1403 sink(file); 1404 sink("("); 1405 char[16] buffer; 1406 sink(intToString(line, buffer[])); 1407 sink("): "); 1408 sink(message); 1409 1410 getAdditionalPrintableInformation((string name, in char[] value) { 1411 sink("\n"); 1412 sink(name); 1413 sink(": "); 1414 sink(value); 1415 }); 1416 1417 // full stack trace 1418 sink("\n----------------\n"); 1419 foreach(str; info) { 1420 sink(str); 1421 sink("\n"); 1422 } 1423 } 1424 /// ditto 1425 final override string toString() { 1426 string s; 1427 toString((in char[] chunk) { s ~= chunk; }); 1428 return s; 1429 } 1430 1431 /++ 1432 Users might like to see additional information with the exception. API consumers should pull this out of properties on your child class, but the parent class might not be able to deal with the arbitrary types at runtime the children can introduce, so bringing them all down to strings simplifies that. 1433 1434 Overrides should always call `super.getAdditionalPrintableInformation(sink);` before adding additional information by calling the sink with other arguments afterward. 1435 1436 You should spare no expense in preparing this information - translate error codes, build rich strings, whatever it takes - to make the information here useful to the reader. 1437 +/ 1438 void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 1439 1440 } 1441 1442 /++ 1443 This is the name of the exception class, suitable for printing. This should be static data (e.g. a string literal). Override it in subclasses. 1444 +/ 1445 string printableExceptionName() const { 1446 return typeid(this).name; 1447 } 1448 1449 /// deliberately hiding `Throwable.msg`. Use [message] and [toString] instead. 1450 @disable final void msg() {} 1451 1452 override const(char)[] message() const { 1453 return super.msg; 1454 } 1455 } 1456 1457 /++ 1458 1459 +/ 1460 class InvalidArgumentsException : ArsdExceptionBase { 1461 static struct InvalidArgument { 1462 string name; 1463 string description; 1464 LimitedVariant givenValue; 1465 } 1466 1467 InvalidArgument[] invalidArguments; 1468 1469 this(InvalidArgument[] invalidArguments, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1470 this.invalidArguments = invalidArguments; 1471 super(functionName, file, line, next); 1472 } 1473 1474 this(string argumentName, string argumentDescription, LimitedVariant givenArgumentValue = LimitedVariant.init, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1475 this([ 1476 InvalidArgument(argumentName, argumentDescription, givenArgumentValue) 1477 ], functionName, file, line, next); 1478 } 1479 1480 this(string argumentName, string argumentDescription, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1481 this(argumentName, argumentDescription, LimitedVariant.init, functionName, file, line, next); 1482 } 1483 1484 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 1485 // FIXME: print the details better 1486 foreach(arg; invalidArguments) 1487 sink("invalidArguments[]", arg.name ~ " " ~ arg.description); 1488 } 1489 } 1490 1491 /++ 1492 Base class for when you've requested a feature that is not available. It may not be available because it is possible, but not yet implemented, or it might be because it is impossible on your operating system. 1493 +/ 1494 class FeatureUnavailableException : ArsdExceptionBase { 1495 this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1496 super(featureName, file, line, next); 1497 } 1498 } 1499 1500 /++ 1501 This means the feature could be done, but I haven't gotten around to implementing it yet. If you email me, I might be able to add it somewhat quickly and get back to you. 1502 +/ 1503 class NotYetImplementedException : FeatureUnavailableException { 1504 this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1505 super(featureName, file, line, next); 1506 } 1507 1508 } 1509 1510 /++ 1511 This means the feature is not supported by your current operating system. You might be able to get it in an update, but you might just have to find an alternate way of doing things. 1512 +/ 1513 class NotSupportedException : FeatureUnavailableException { 1514 this(string featureName, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1515 super(featureName, file, line, next); 1516 } 1517 } 1518 1519 /++ 1520 This is a generic exception with attached arguments. It is used when I had to throw something but didn't want to write a new class. 1521 1522 You can catch an ArsdException to get its passed arguments out. 1523 1524 You can pass either a base class or a string as `Type`. 1525 1526 See the examples for how to use it. 1527 +/ 1528 template ArsdException(alias Type, DataTuple...) { 1529 static if(DataTuple.length) 1530 alias Parent = ArsdException!(Type, DataTuple[0 .. $-1]); 1531 else 1532 alias Parent = ArsdExceptionBase; 1533 1534 class ArsdException : Parent { 1535 DataTuple data; 1536 1537 this(DataTuple data, string file = __FILE__, size_t line = __LINE__) { 1538 this.data = data; 1539 static if(is(Parent == ArsdExceptionBase)) 1540 super(null, file, line); 1541 else 1542 super(data[0 .. $-1], file, line); 1543 } 1544 1545 static opCall(R...)(R r, string file = __FILE__, size_t line = __LINE__) { 1546 return new ArsdException!(Type, DataTuple, R)(r, file, line); 1547 } 1548 1549 override string printableExceptionName() const { 1550 static if(DataTuple.length) 1551 enum str = "ArsdException!(" ~ Type.stringof ~ ", " ~ DataTuple.stringof[1 .. $-1] ~ ")"; 1552 else 1553 enum str = "ArsdException!" ~ Type.stringof; 1554 return str; 1555 } 1556 1557 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 1558 ArsdExceptionBase.getAdditionalPrintableInformation(sink); 1559 1560 foreach(idx, datum; data) { 1561 enum int lol = cast(int) idx; 1562 enum key = "[" ~ lol.stringof ~ "] " ~ DataTuple[idx].stringof; 1563 sink(key, toStringInternal(datum)); 1564 } 1565 } 1566 } 1567 } 1568 1569 /// This example shows how you can throw and catch the ad-hoc exception types. 1570 unittest { 1571 // you can throw and catch by matching the string and argument types 1572 try { 1573 // throw it with parenthesis after the template args (it uses opCall to construct) 1574 throw ArsdException!"Test"(); 1575 // you could also `throw new ArsdException!"test";`, but that gets harder with args 1576 // as we'll see in the following example 1577 assert(0); // remove from docs 1578 } catch(ArsdException!"Test" e) { // catch it without them 1579 // this has no useful information except for the type 1580 // but you can catch it like this and it is still more than generic Exception 1581 } 1582 1583 // an exception's job is to deliver useful information up the chain 1584 // and you can do that easily by passing arguments: 1585 1586 try { 1587 throw ArsdException!"Test"(4, "four"); 1588 // you could also `throw new ArsdException!("Test", int, string)(4, "four")` 1589 // but now you start to see how the opCall convenience constructor simplifies things 1590 assert(0); // remove from docs 1591 } catch(ArsdException!("Test", int, string) e) { // catch it and use info by specifying types 1592 assert(e.data[0] == 4); // and extract arguments like this 1593 assert(e.data[1] == "four"); 1594 } 1595 1596 // a throw site can add additional information without breaking code that catches just some 1597 // generally speaking, each additional argument creates a new subclass on top of the previous args 1598 // so you can cast 1599 1600 try { 1601 throw ArsdException!"Test"(4, "four", 9); 1602 assert(0); // remove from docs 1603 } catch(ArsdException!("Test", int, string) e) { // this catch still works 1604 assert(e.data[0] == 4); 1605 assert(e.data[1] == "four"); 1606 // but if you were to print it, all the members would be there 1607 // import std.stdio; writeln(e); // would show something like: 1608 /+ 1609 ArsdException!("Test", int, string, int)@file.d(line): 1610 [0] int: 4 1611 [1] string: four 1612 [2] int: 9 1613 +/ 1614 // indicating that there's additional information available if you wanted to process it 1615 1616 // and meanwhile: 1617 ArsdException!("Test", int) e2 = e; // this implicit cast works thanks to the parent-child relationship 1618 ArsdException!"Test" e3 = e; // this works too, the base type/string still matches 1619 1620 // so catching those types would work too 1621 } 1622 } 1623 1624 /++ 1625 A tagged union that holds an error code from system apis, meaning one from Windows GetLastError() or C's errno. 1626 1627 You construct it with `SystemErrorCode(thing)` and the overloaded constructor tags and stores it. 1628 +/ 1629 struct SystemErrorCode { 1630 /// 1631 enum Type { 1632 errno, /// 1633 win32 /// 1634 } 1635 1636 const Type type; /// 1637 const int code; /// You should technically cast it back to DWORD if it is a win32 code 1638 1639 /++ 1640 C/unix error are typed as signed ints... 1641 Windows' errors are typed DWORD, aka unsigned... 1642 1643 so just passing them straight up will pick the right overload here to set the tag. 1644 +/ 1645 this(int errno) { 1646 this.type = Type.errno; 1647 this.code = errno; 1648 } 1649 1650 /// ditto 1651 this(uint win32) { 1652 this.type = Type.win32; 1653 this.code = win32; 1654 } 1655 1656 /++ 1657 Returns if the code indicated success. 1658 1659 Please note that many calls do not actually set a code to success, but rather just don't touch it. Thus this may only be true on `init`. 1660 +/ 1661 bool wasSuccessful() const { 1662 final switch(type) { 1663 case Type.errno: 1664 return this.code == 0; 1665 case Type.win32: 1666 return this.code == 0; 1667 } 1668 } 1669 1670 /++ 1671 Constructs a string containing both the code and the explanation string. 1672 +/ 1673 string toString() const { 1674 return "[" ~ codeAsString ~ "] " ~ errorString; 1675 } 1676 1677 /++ 1678 The numeric code itself as a string. 1679 1680 See [errorString] for a text explanation of the code. 1681 +/ 1682 string codeAsString() const { 1683 char[16] buffer; 1684 final switch(type) { 1685 case Type.errno: 1686 return intToString(code, buffer[]).idup; 1687 case Type.win32: 1688 buffer[0 .. 2] = "0x"; 1689 return buffer[0 .. 2 + intToString(cast(uint) code, buffer[2 .. $], IntToStringArgs().withRadix(16).withPadding(8)).length].idup; 1690 } 1691 } 1692 1693 /++ 1694 A text explanation of the code. See [codeAsString] for a string representation of the numeric representation. 1695 +/ 1696 string errorString() const { 1697 final switch(type) { 1698 case Type.errno: 1699 import core.stdc.string; 1700 auto strptr = strerror(code); 1701 auto orig = strptr; 1702 int len; 1703 while(*strptr++) { 1704 len++; 1705 } 1706 1707 return orig[0 .. len].idup; 1708 case Type.win32: 1709 version(Windows) { 1710 wchar[256] buffer; 1711 auto size = FormatMessageW( 1712 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 1713 null, 1714 code, 1715 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 1716 buffer.ptr, 1717 buffer.length, 1718 null 1719 ); 1720 1721 return makeUtf8StringFromWindowsString(buffer[0 .. size]).stripInternal; 1722 } else { 1723 return null; 1724 } 1725 } 1726 } 1727 } 1728 1729 /++ 1730 1731 +/ 1732 struct SavedArgument { 1733 string name; 1734 LimitedVariant value; 1735 } 1736 1737 /++ 1738 1739 +/ 1740 class SystemApiException : ArsdExceptionBase { 1741 this(string msg, int originalErrorNo, scope SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1742 this(msg, SystemErrorCode(originalErrorNo), args, file, line, next); 1743 } 1744 1745 version(Windows) 1746 this(string msg, DWORD windowsError, scope SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1747 this(msg, SystemErrorCode(windowsError), args, file, line, next); 1748 } 1749 1750 this(string msg, SystemErrorCode code, SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1751 this.errorCode = code; 1752 1753 // discard stuff that won't fit 1754 if(args.length > this.args.length) 1755 args = args[0 .. this.args.length]; 1756 1757 this.args[0 .. args.length] = args[]; 1758 1759 super(msg, file, line, next); 1760 } 1761 1762 /++ 1763 1764 +/ 1765 const SystemErrorCode errorCode; 1766 1767 /++ 1768 1769 +/ 1770 const SavedArgument[8] args; 1771 1772 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 1773 super.getAdditionalPrintableInformation(sink); 1774 sink("Error code", errorCode.toString()); 1775 1776 foreach(arg; args) 1777 if(arg.name !is null) 1778 sink(arg.name, arg.value.toString()); 1779 } 1780 1781 } 1782 1783 /++ 1784 The low level use of this would look like `throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError())` but it is meant to be used from higher level things like [Win32Enforce]. 1785 1786 History: 1787 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 1788 +/ 1789 alias WindowsApiException = SystemApiException; 1790 1791 /++ 1792 History: 1793 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 1794 +/ 1795 alias ErrnoApiException = SystemApiException; 1796 1797 /++ 1798 Calls the C API function `fn`. If it returns an error value, it throws an [ErrnoApiException] (or subclass) after getting `errno`. 1799 +/ 1800 template ErrnoEnforce(alias fn, alias errorValue = void) { 1801 static if(is(typeof(fn) Return == return)) 1802 static if(is(typeof(fn) Params == __parameters)) { 1803 static if(is(errorValue == void)) { 1804 static if(is(typeof(null) : Return)) 1805 enum errorValueToUse = null; 1806 else static if(is(Return : long)) 1807 enum errorValueToUse = -1; 1808 else 1809 static assert(0, "Please pass the error value"); 1810 } else { 1811 enum errorValueToUse = errorValue; 1812 } 1813 1814 Return ErrnoEnforce(Params params, ArgSentinel sentinel = ArgSentinel.init, string file = __FILE__, size_t line = __LINE__) { 1815 import core.stdc.errno; 1816 1817 Return value = fn(params); 1818 1819 if(value == errorValueToUse) { 1820 SavedArgument[] args; // FIXME 1821 /+ 1822 static foreach(idx; 0 .. Params.length) 1823 args ~= SavedArgument( 1824 __traits(identifier, Params[idx .. idx + 1]), 1825 params[idx] 1826 ); 1827 +/ 1828 throw new ErrnoApiException(__traits(identifier, fn), errno, args, file, line); 1829 } 1830 1831 return value; 1832 } 1833 } 1834 } 1835 1836 version(Windows) { 1837 /++ 1838 Calls the Windows API function `fn`. If it returns an error value, it throws a [WindowsApiException] (or subclass) after calling `GetLastError()`. 1839 +/ 1840 template Win32Enforce(alias fn, alias errorValue = void) { 1841 static if(is(typeof(fn) Return == return)) 1842 static if(is(typeof(fn) Params == __parameters)) { 1843 static if(is(errorValue == void)) { 1844 static if(is(Return == BOOL)) 1845 enum errorValueToUse = false; 1846 else static if(is(Return : HANDLE)) 1847 enum errorValueToUse = NULL; 1848 else static if(is(Return == DWORD)) 1849 enum errorValueToUse = cast(DWORD) 0xffffffff; 1850 else 1851 static assert(0, "Please pass the error value"); 1852 } else { 1853 enum errorValueToUse = errorValue; 1854 } 1855 1856 Return Win32Enforce(Params params, ArgSentinel sentinel = ArgSentinel.init, string file = __FILE__, size_t line = __LINE__) { 1857 Return value = fn(params); 1858 1859 if(value == errorValueToUse) { 1860 auto error = GetLastError(); 1861 SavedArgument[] args; // FIXME 1862 throw new WindowsApiException(__traits(identifier, fn), error, args, file, line); 1863 } 1864 1865 return value; 1866 } 1867 } 1868 } 1869 1870 } 1871 1872 /+ 1873 =============== 1874 EVENT LOOP CORE 1875 =============== 1876 +/ 1877 1878 /+ 1879 UI threads 1880 need to get window messages in addition to all the other jobs 1881 I/O Worker threads 1882 need to get commands for read/writes, run them, and send the reply back. not necessary on Windows 1883 if interrupted, check cancel flags. 1884 CPU Worker threads 1885 gets functions, runs them, send reply back. should send a cancel flag to periodically check 1886 Task worker threads 1887 runs fibers and multiplexes them 1888 1889 1890 General procedure: 1891 issue the read/write command 1892 if it would block on linux, epoll associate it. otherwise do the callback immediately 1893 1894 callbacks have default affinity to the current thread, meaning their callbacks always run here 1895 accepts can usually be dispatched to any available thread tho 1896 1897 // In other words, a single thread can be associated with, at most, one I/O completion port. 1898 1899 Realistically, IOCP only used if there is no thread affinity. If there is, just do overlapped w/ sleepex. 1900 1901 1902 case study: http server 1903 1904 1) main thread starts the server. it does an accept loop with no thread affinity. the main thread does NOT check the global queue (the iocp/global epoll) 1905 2) connections come in and are assigned to first available thread via the iocp/global epoll 1906 3) these run local event loops until the connection task is finished 1907 1908 EVENT LOOP TYPES: 1909 1) main ui thread - MsgWaitForMultipleObjectsEx / epoll on the local ui. it does NOT check the any worker thread thing! 1910 The main ui thread should never terminate until the program is ready to close. 1911 You can have additional ui threads in theory but im not really gonna support that in full; most things will assume there is just the one. simpledisplay's gui thread is the primary if it exists. (and sdpy will prolly continue to be threaded the way it is now) 1912 1913 The biggest complication is the TerminalDirectToEmulator, where the primary ui thread is NOT the thread that runs `main` 1914 2) worker thread GetQueuedCompletionStatusEx / epoll on the local thread fd and the global epoll fd 1915 3) local event loop - check local things only. SleepEx / epoll on local thread fd. This more of a compatibility hack for `waitForCompletion` outside a fiber. 1916 1917 i'll use: 1918 * QueueUserAPC to send interruptions to a worker thread 1919 * PostQueuedCompletionStatus is to send interruptions to any available thread. 1920 * PostMessage to a window 1921 * ??? to a fiber task 1922 1923 I also need a way to de-duplicate events in the queue so if you try to push the same thing it won't trigger multiple times.... I might want to keep a duplicate of the thing... really, what I'd do is post the "event wake up" message and keep the queue in my own thing. (WM_PAINT auto-coalesces) 1924 1925 Destructors need to be able to post messages back to a specific task to queue thread-affinity cleanup. This must be GC safe. 1926 1927 A task might want to wait on certain events. If the task is a fiber, it yields and gets called upon the event. If the task is a thread, it really has to call the event loop... which can be a loop of loops we want to avoid. `waitForCompletion` is more often gonna be used just to run the loop at top level tho... it might not even check for the global info availability so it'd run the local thing only. 1928 1929 APCs should not themselves enter an alterable wait cuz it can stack overflow. So generally speaking, they should avoid calling fibers or other event loops. 1930 +/ 1931 1932 /++ 1933 You can also pass a handle to a specific thread, if you have one. 1934 +/ 1935 enum ThreadToRunIn { 1936 /++ 1937 The callback should be only run by the same thread that set it. 1938 +/ 1939 CurrentThread, 1940 /++ 1941 The UI thread is a special one - it is the supervisor of the workers and the controller of gui and console handles. It is the first thread to call [arsd_core_init] actively running an event loop unless there is a thread that has actively asserted the ui supervisor role. FIXME is this true after i implemen it? 1942 1943 A ui thread should be always quickly responsive to new events. 1944 1945 There should only be one main ui thread, in which simpledisplay and minigui can be used. 1946 1947 Other threads can run like ui threads, but are considered temporary and only concerned with their own needs (it is the default style of loop 1948 for an undeclared thread but will not receive messages from other threads unless there is no other option) 1949 1950 1951 Ad-Hoc thread - something running an event loop that isn't another thing 1952 Controller thread - running an explicit event loop instance set as not a task runner or blocking worker 1953 UI thread - simpledisplay's event loop, which it will require remain live for the duration of the program (running two .eventLoops without a parent EventLoop instance will become illegal, throwing at runtime if it happens telling people to change their code) 1954 1955 Windows HANDLES will always be listened on the thread itself that is requesting, UNLESS it is a worker/helper thread, in which case it goes to a coordinator thread. since it prolly can't rely on the parent per se this will have to be one created by arsd core init, UNLESS the parent is inside an explicit EventLoop structure. 1956 1957 All use the MsgWaitForMultipleObjectsEx pattern 1958 1959 1960 +/ 1961 UiThread, 1962 /++ 1963 The callback can be called from any available worker thread. It will be added to a global queue and the first thread to see it will run it. 1964 1965 These will not run on the UI thread unless there is no other option on the platform (and all platforms this lib supports have other options). 1966 1967 These are expected to run cooperatively multitasked things; functions that frequently yield as they wait on other tasks. Think a fiber. 1968 1969 A task runner should be generally responsive to new events. 1970 +/ 1971 AnyAvailableTaskRunnerThread, 1972 /++ 1973 These are expected to run longer blocking, but independent operations. Think an individual function with no context. 1974 1975 A blocking worker can wait hundreds of milliseconds between checking for new events. 1976 +/ 1977 AnyAvailableBlockingWorkerThread, 1978 /++ 1979 The callback will be duplicated across all threads known to the arsd.core event loop. 1980 1981 It adds it to an immutable queue that each thread will go through... might just replace with an exit() function. 1982 1983 1984 so to cancel all associated tasks for like a web server, it could just have the tasks atomicAdd to a counter and subtract when they are finished. Then you have a single semaphore you signal the number of times you have an active thing and wait for them to acknowledge it. 1985 1986 threads should report when they start running the loop and they really should report when they terminate but that isn't reliable 1987 1988 1989 hmmm what if: all user-created threads (the public api) count as ui threads. only ones created in here are task runners or helpers. ui threads can wait on a global event to exit. 1990 1991 there's still prolly be one "the" ui thread, which does the handle listening on windows and is the one sdpy wants. 1992 +/ 1993 BroadcastToAllThreads, 1994 } 1995 1996 /++ 1997 Initializes the arsd core event loop and creates its worker threads. You don't actually have to call this, since the first use of an arsd.core function that requires it will call it implicitly, but calling it yourself gives you a chance to control the configuration more explicitly if you want to. 1998 +/ 1999 void arsd_core_init(int numberOfWorkers = 0) { 2000 2001 } 2002 2003 version(Windows) 2004 class WindowsHandleReader_ex { 2005 // Windows handles are always dispatched to the main ui thread, which can then send a command back to a worker thread to run the callback if needed 2006 this(HANDLE handle) {} 2007 } 2008 2009 version(Posix) 2010 class PosixFdReader_ex { 2011 // posix readers can just register with whatever instance we want to handle the callback 2012 } 2013 2014 /++ 2015 2016 +/ 2017 interface ICoreEventLoop { 2018 /++ 2019 Runs the event loop for this thread until the `until` delegate returns `true`. 2020 +/ 2021 final void run(scope bool delegate() until) { 2022 while(!exitApplicationRequested && !until()) { 2023 runOnce(); 2024 } 2025 } 2026 2027 private __gshared bool exitApplicationRequested; 2028 2029 final static void exitApplication() { 2030 exitApplicationRequested = true; 2031 // FIXME: wake up all the threads 2032 } 2033 2034 /++ 2035 Returns details from a call to [runOnce]. Use the named methods here for details, or it can be used in a `while` loop directly thanks to its `opCast` automatic conversion to `bool`. 2036 2037 History: 2038 Added December 28, 2023 2039 +/ 2040 static struct RunOnceResult { 2041 enum Possibilities { 2042 CarryOn, 2043 LocalExit, 2044 GlobalExit, 2045 Interrupted 2046 2047 } 2048 Possibilities result; 2049 2050 /++ 2051 Returns `true` if the event loop should generally continue. 2052 2053 Might be false if the local loop was exited or if the application is supposed to exit. If this is `false`, check [applicationExitRequested] to determine if you should move on to other work or start your final cleanup process. 2054 +/ 2055 bool shouldContinue() const { 2056 return result == Possibilities.CarryOn; 2057 } 2058 2059 /++ 2060 Returns `true` if [ICoreEventLoop.exitApplication] was called during this event, or if the user or operating system has requested the application exit. 2061 2062 Details might be available through other means. 2063 +/ 2064 bool applicationExitRequested() const { 2065 return result == Possibilities.GlobalExit; 2066 } 2067 2068 /++ 2069 Returns [shouldContinue] when used in a context for an implicit bool (e.g. `if` statements). 2070 +/ 2071 bool opCast(T : bool)() const { 2072 reutrn shouldContinue(); 2073 } 2074 } 2075 2076 /++ 2077 Runs a single iteration of the event loop for this thread. It will return when the first thing happens, but that thing might be totally uninteresting to anyone, or it might trigger significant work you'll wait on. 2078 2079 Note that running this externally instead of `run` gives only the $(I illusion) of control. You're actually better off setting a recurring timer if you need things to run on a clock tick, or a single-shot timer for a one time event. They're more likely to be called on schedule inside this function than outside it. 2080 2081 Parameters: 2082 timeout = a timeout value for an idle loop. There is no guarantee you won't return earlier or later than this; the function might run longer than the timeout if it has work to do. Pass `Duration.max` (the default) for an infinite duration timeout (but remember, once it finds work to do, including a false-positive wakeup or interruption by the operating system, it will return early anyway). 2083 2084 History: 2085 Prior to December 28, 2023, it returned `void` and took no arguments. This change is breaking, but since the entire module is documented as unstable, it was permitted to happen as that document provided prior notice. 2086 +/ 2087 RunOnceResult runOnce(Duration timeout = Duration.max); 2088 2089 /++ 2090 Adds a delegate to be called on each loop iteration, called based on the `timingFlags`. 2091 2092 2093 The order in which the delegates are called is undefined and may change with each iteration of the loop. Additionally, when and how many times a loop iterates is undefined; multiple events might be handled by each iteration, or sometimes, nothing will be handled and it woke up spuriously. Your delegates need to be ok with all of this. 2094 2095 Parameters: 2096 dg = the delegate to call 2097 timingFlags = 2098 0: never actually run the function; it can assert error if you pass this 2099 1: run before each loop OS wait call 2100 2: run after each loop OS wait call 2101 3: run both before and after each OS wait call 2102 4: single shot? 2103 8: no-coalesce? (if after was just run, it will skip the before loops unless this flag is set) 2104 2105 +/ 2106 void addDelegateOnLoopIteration(void delegate() dg, uint timingFlags); 2107 2108 final void addDelegateOnLoopIteration(void function() dg, uint timingFlags) { 2109 addDelegateOnLoopIteration(toDelegate(dg), timingFlags); 2110 } 2111 2112 // to send messages between threads, i'll queue up a function that just call dispatchMessage. can embed the arg inside the callback helper prolly. 2113 // tho i might prefer to actually do messages w/ run payloads so it is easier to deduplicate i can still dedupe by insepcting the call args so idk 2114 2115 version(Posix) { 2116 @mustuse 2117 static struct UnregisterToken { 2118 private CoreEventLoopImplementation impl; 2119 private int fd; 2120 private CallbackHelper cb; 2121 2122 /++ 2123 Unregisters the file descriptor from the event loop and releases the reference to the callback held by the event loop (which will probably free it). 2124 2125 You must call this when you're done. Normally, this will be right before you close the fd (Which is often after the other side closes it, meaning you got a 0 length read). 2126 +/ 2127 void unregister() { 2128 assert(impl !is null, "Cannot reuse unregister token"); 2129 2130 version(Arsd_core_epoll) { 2131 impl.unregisterFd(fd); 2132 } else version(Arsd_core_kqueue) { 2133 // intentionally blank - all registrations are one-shot there 2134 // FIXME: actually it might not have gone off yet, in that case we do need to delete the filter 2135 } else version(EmptyCoreEvent) { 2136 2137 } 2138 else static assert(0); 2139 2140 cb.release(); 2141 this = typeof(this).init; 2142 } 2143 } 2144 2145 @mustuse 2146 static struct RearmToken { 2147 private bool readable; 2148 private CoreEventLoopImplementation impl; 2149 private int fd; 2150 private CallbackHelper cb; 2151 private uint flags; 2152 2153 /++ 2154 Calls [UnregisterToken.unregister] 2155 +/ 2156 void unregister() { 2157 assert(impl !is null, "cannot reuse rearm token after unregistering it"); 2158 2159 version(Arsd_core_epoll) { 2160 impl.unregisterFd(fd); 2161 } else version(Arsd_core_kqueue) { 2162 // intentionally blank - all registrations are one-shot there 2163 // FIXME: actually it might not have gone off yet, in that case we do need to delete the filter 2164 } else version(EmptyCoreEvent) { 2165 2166 } else static assert(0); 2167 2168 cb.release(); 2169 this = typeof(this).init; 2170 } 2171 2172 /++ 2173 Rearms the event so you will get another callback next time it is ready. 2174 +/ 2175 void rearm() { 2176 assert(impl !is null, "cannot reuse rearm token after unregistering it"); 2177 impl.rearmFd(this); 2178 } 2179 } 2180 2181 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb); 2182 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb); 2183 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb); 2184 } 2185 2186 version(Windows) { 2187 @mustuse 2188 static struct UnregisterToken { 2189 private CoreEventLoopImplementation impl; 2190 private HANDLE handle; 2191 private CallbackHelper cb; 2192 2193 /++ 2194 Unregisters the handle from the event loop and releases the reference to the callback held by the event loop (which will probably free it). 2195 2196 You must call this when you're done. Normally, this will be right before you close the handle. 2197 +/ 2198 void unregister() { 2199 assert(impl !is null, "Cannot reuse unregister token"); 2200 2201 impl.unregisterHandle(handle, cb); 2202 2203 cb.release(); 2204 this = typeof(this).init; 2205 } 2206 } 2207 2208 UnregisterToken addCallbackOnHandleReady(HANDLE handle, CallbackHelper cb); 2209 } 2210 } 2211 2212 /++ 2213 Get the event loop associated with this thread 2214 +/ 2215 ICoreEventLoop getThisThreadEventLoop(EventLoopType type = EventLoopType.AdHoc) { 2216 static ICoreEventLoop loop; 2217 if(loop is null) 2218 loop = new CoreEventLoopImplementation(); 2219 return loop; 2220 } 2221 2222 /++ 2223 The internal types that will be exposed through other api things. 2224 +/ 2225 package(arsd) enum EventLoopType { 2226 /++ 2227 The event loop is being run temporarily and the thread doesn't promise to keep running it. 2228 +/ 2229 AdHoc, 2230 /++ 2231 The event loop struct has been instantiated at top level. Its destructor will run when the 2232 function exits, which is only at the end of the entire block of work it is responsible for. 2233 2234 It must be in scope for the whole time the arsd event loop functions are expected to be used 2235 (meaning it should generally be top-level in `main`) 2236 +/ 2237 Explicit, 2238 /++ 2239 A specialization of `Explicit`, so all the same rules apply there, but this is specifically the event loop coming from simpledisplay or minigui. It will run for the duration of the UI's existence. 2240 +/ 2241 Ui, 2242 /++ 2243 A special event loop specifically for threads that listen to the task runner queue and handle I/O events from running tasks. Typically, a task runner runs cooperatively multitasked coroutines (so they prefer not to block the whole thread). 2244 +/ 2245 TaskRunner, 2246 /++ 2247 A special event loop specifically for threads that listen to the helper function request queue. Helper functions are expected to run independently for a somewhat long time (them blocking the thread for some time is normal) and send a reply message back to the requester. 2248 +/ 2249 HelperWorker 2250 } 2251 2252 /+ 2253 Tasks are given an object to talk to their parent... can be a dialog where it is like 2254 2255 sendBuffer 2256 waitForWordToProceed 2257 2258 in a loop 2259 2260 2261 Tasks are assigned to a worker thread and may share it with other tasks. 2262 +/ 2263 2264 2265 // the GC may not be able to see this! remember, it can be hidden inside kernel buffers 2266 version(HasThread) package(arsd) class CallbackHelper { 2267 import core.memory; 2268 2269 void call() { 2270 if(callback) 2271 callback(); 2272 } 2273 2274 void delegate() callback; 2275 void*[3] argsStore; 2276 2277 void addref() { 2278 atomicOp!"+="(refcount, 1); 2279 } 2280 2281 void release() { 2282 if(atomicOp!"-="(refcount, 1) <= 0) { 2283 if(flags & 1) 2284 GC.removeRoot(cast(void*) this); 2285 } 2286 } 2287 2288 private shared(int) refcount; 2289 private uint flags; 2290 2291 this(void function() callback) { 2292 this( () { callback(); } ); 2293 } 2294 2295 this(void delegate() callback, bool addRoot = true) { 2296 if(addRoot) { 2297 GC.addRoot(cast(void*) this); 2298 this.flags |= 1; 2299 } 2300 2301 this.addref(); 2302 this.callback = callback; 2303 } 2304 } 2305 2306 /++ 2307 This represents a file. Technically, file paths aren't actually strings (for example, on Linux, they need not be valid utf-8, while a D string is supposed to be), even though we almost always use them like that. 2308 2309 This type is meant to represent a filename / path. I might not keep it around. 2310 +/ 2311 struct FilePath { 2312 string path; 2313 2314 bool isNull() { 2315 return path is null; 2316 } 2317 2318 bool opCast(T:bool)() { 2319 return !isNull; 2320 } 2321 2322 string toString() { 2323 return path; 2324 } 2325 2326 //alias toString this; 2327 } 2328 2329 /++ 2330 Represents a generic async, waitable request. 2331 +/ 2332 class AsyncOperationRequest { 2333 /++ 2334 Actually issues the request, starting the operation. 2335 +/ 2336 abstract void start(); 2337 /++ 2338 Cancels the request. This will cause `isComplete` to return true once the cancellation has been processed, but [AsyncOperationResponse.wasSuccessful] will return `false` (unless it completed before the cancellation was processed, in which case it is still allowed to finish successfully). 2339 2340 After cancelling a request, you should still wait for it to complete to ensure that the task has actually released its resources before doing anything else on it. 2341 2342 Once a cancellation request has been sent, it cannot be undone. 2343 +/ 2344 abstract void cancel(); 2345 2346 /++ 2347 Returns `true` if the operation has been completed. It may be completed successfully, cancelled, or have errored out - to check this, call [waitForCompletion] and check the members on the response object. 2348 +/ 2349 abstract bool isComplete(); 2350 /++ 2351 Waits until the request has completed - successfully or otherwise - and returns the response object. It will run an ad-hoc event loop that may call other callbacks while waiting. 2352 2353 The response object may be embedded in the request object - do not reuse the request until you are finished with the response and do not keep the response around longer than you keep the request. 2354 2355 2356 Note to implementers: all subclasses should override this and return their specific response object. You can use the top-level `waitForFirstToCompleteByIndex` function with a single-element static array to help with the implementation. 2357 +/ 2358 abstract AsyncOperationResponse waitForCompletion(); 2359 2360 /++ 2361 2362 +/ 2363 // abstract void repeat(); 2364 } 2365 2366 /++ 2367 2368 +/ 2369 interface AsyncOperationResponse { 2370 /++ 2371 Returns true if the request completed successfully, finishing what it was supposed to. 2372 2373 Should be set to `false` if the request was cancelled before completing or encountered an error. 2374 +/ 2375 bool wasSuccessful(); 2376 } 2377 2378 /++ 2379 It returns the $(I request) so you can identify it more easily. `request.waitForCompletion()` is guaranteed to return the response without any actual wait, since it is already complete when this function returns. 2380 2381 Please note that "completion" is not necessary successful completion; a request being cancelled or encountering an error also counts as it being completed. 2382 2383 The `waitForFirstToCompleteByIndex` version instead returns the index of the array entry that completed first. 2384 2385 It is your responsibility to remove the completed request from the array before calling the function again, since any request already completed will always be immediately returned. 2386 2387 You might prefer using [asTheyComplete], which will give each request as it completes and loop over until all of them are complete. 2388 2389 Returns: 2390 `null` or `requests.length` if none completed before returning. 2391 +/ 2392 AsyncOperationRequest waitForFirstToComplete(AsyncOperationRequest[] requests...) { 2393 auto idx = waitForFirstToCompleteByIndex(requests); 2394 if(idx == requests.length) 2395 return null; 2396 return requests[idx]; 2397 } 2398 /// ditto 2399 size_t waitForFirstToCompleteByIndex(AsyncOperationRequest[] requests...) { 2400 size_t helper() { 2401 foreach(idx, request; requests) 2402 if(request.isComplete()) 2403 return idx; 2404 return requests.length; 2405 } 2406 2407 auto idx = helper(); 2408 // if one is already done, return it 2409 if(idx != requests.length) 2410 return idx; 2411 2412 // otherwise, run the ad-hoc event loop until one is 2413 // FIXME: what if we are inside a fiber? 2414 auto el = getThisThreadEventLoop(); 2415 el.run(() => (idx = helper()) != requests.length); 2416 2417 return idx; 2418 } 2419 2420 /++ 2421 Waits for all the `requests` to complete, giving each one through the range interface as it completes. 2422 2423 This meant to be used in a foreach loop. 2424 2425 The `requests` array and its contents must remain valid for the lifetime of the returned range. Its contents may be shuffled as the requests complete (the implementation works through an unstable sort+remove). 2426 +/ 2427 AsTheyCompleteRange asTheyComplete(AsyncOperationRequest[] requests...) { 2428 return AsTheyCompleteRange(requests); 2429 } 2430 /// ditto 2431 struct AsTheyCompleteRange { 2432 AsyncOperationRequest[] requests; 2433 2434 this(AsyncOperationRequest[] requests) { 2435 this.requests = requests; 2436 2437 if(requests.length == 0) 2438 return; 2439 2440 // wait for first one to complete, then move it to the front of the array 2441 moveFirstCompleteToFront(); 2442 } 2443 2444 private void moveFirstCompleteToFront() { 2445 auto idx = waitForFirstToCompleteByIndex(requests); 2446 2447 auto tmp = requests[0]; 2448 requests[0] = requests[idx]; 2449 requests[idx] = tmp; 2450 } 2451 2452 bool empty() { 2453 return requests.length == 0; 2454 } 2455 2456 void popFront() { 2457 assert(!empty); 2458 /+ 2459 this needs to 2460 1) remove the front of the array as being already processed (unless it is the initial priming call) 2461 2) wait for one of them to complete 2462 3) move the complete one to the front of the array 2463 +/ 2464 2465 requests[0] = requests[$-1]; 2466 requests = requests[0 .. $-1]; 2467 2468 if(requests.length) 2469 moveFirstCompleteToFront(); 2470 } 2471 2472 AsyncOperationRequest front() { 2473 return requests[0]; 2474 } 2475 } 2476 2477 version(Windows) { 2478 alias NativeFileHandle = HANDLE; /// 2479 alias NativeSocketHandle = SOCKET; /// 2480 alias NativePipeHandle = HANDLE; /// 2481 } else version(Posix) { 2482 alias NativeFileHandle = int; /// 2483 alias NativeSocketHandle = int; /// 2484 alias NativePipeHandle = int; /// 2485 } 2486 2487 /++ 2488 An `AbstractFile` represents a file handle on the operating system level. You cannot do much with it. 2489 +/ 2490 version(HasFile) class AbstractFile { 2491 private { 2492 NativeFileHandle handle; 2493 } 2494 2495 /++ 2496 +/ 2497 enum OpenMode { 2498 readOnly, /// C's "r", the file is read 2499 writeWithTruncation, /// C's "w", the file is blanked upon opening so it only holds what you write 2500 appendOnly, /// C's "a", writes will always be appended to the file 2501 readAndWrite /// C's "r+", writes will overwrite existing parts of the file based on where you seek (default is at the beginning) 2502 } 2503 2504 /++ 2505 +/ 2506 enum RequirePreexisting { 2507 no, 2508 yes 2509 } 2510 2511 /+ 2512 enum SpecialFlags { 2513 randomAccessExpected, /// FILE_FLAG_SEQUENTIAL_SCAN is turned off and posix_fadvise(POSIX_FADV_SEQUENTIAL) 2514 skipCache, /// O_DSYNC, FILE_FLAG_NO_BUFFERING and maybe WRITE_THROUGH. note that metadata still goes through the cache, FlushFileBuffers and fsync can still do those 2515 temporary, /// FILE_ATTRIBUTE_TEMPORARY on Windows, idk how to specify on linux. also FILE_FLAG_DELETE_ON_CLOSE can be combined to make a (almost) all memory file. kinda like a private anonymous mmap i believe. 2516 deleteWhenClosed, /// Windows has a flag for this but idk if it is of any real use 2517 async, /// open it in overlapped mode, all reads and writes must then provide an offset. Only implemented on Windows 2518 } 2519 +/ 2520 2521 /++ 2522 2523 +/ 2524 protected this(bool async, FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0) { 2525 version(Windows) { 2526 DWORD access; 2527 DWORD creation; 2528 2529 final switch(mode) { 2530 case OpenMode.readOnly: 2531 access = GENERIC_READ; 2532 creation = OPEN_EXISTING; 2533 break; 2534 case OpenMode.writeWithTruncation: 2535 access = GENERIC_WRITE; 2536 2537 final switch(require) { 2538 case RequirePreexisting.no: 2539 creation = CREATE_ALWAYS; 2540 break; 2541 case RequirePreexisting.yes: 2542 creation = TRUNCATE_EXISTING; 2543 break; 2544 } 2545 break; 2546 case OpenMode.appendOnly: 2547 access = FILE_APPEND_DATA; 2548 2549 final switch(require) { 2550 case RequirePreexisting.no: 2551 creation = CREATE_ALWAYS; 2552 break; 2553 case RequirePreexisting.yes: 2554 creation = OPEN_EXISTING; 2555 break; 2556 } 2557 break; 2558 case OpenMode.readAndWrite: 2559 access = GENERIC_READ | GENERIC_WRITE; 2560 2561 final switch(require) { 2562 case RequirePreexisting.no: 2563 creation = CREATE_NEW; 2564 break; 2565 case RequirePreexisting.yes: 2566 creation = OPEN_EXISTING; 2567 break; 2568 } 2569 break; 2570 } 2571 2572 WCharzBuffer wname = WCharzBuffer(filename.path); 2573 2574 auto handle = CreateFileW( 2575 wname.ptr, 2576 access, 2577 FILE_SHARE_READ, 2578 null, 2579 creation, 2580 FILE_ATTRIBUTE_NORMAL | (async ? FILE_FLAG_OVERLAPPED : 0), 2581 null 2582 ); 2583 2584 if(handle == INVALID_HANDLE_VALUE) { 2585 // FIXME: throw the filename and other params here too 2586 SavedArgument[3] args; 2587 args[0] = SavedArgument("filename", LimitedVariant(filename.path)); 2588 args[1] = SavedArgument("access", LimitedVariant(access, 2)); 2589 args[2] = SavedArgument("requirePreexisting", LimitedVariant(require == RequirePreexisting.yes)); 2590 throw new WindowsApiException("CreateFileW", GetLastError(), args[]); 2591 } 2592 2593 this.handle = handle; 2594 } else version(Posix) { 2595 import core.sys.posix.unistd; 2596 import core.sys.posix.fcntl; 2597 2598 CharzBuffer namez = CharzBuffer(filename.path); 2599 int flags; 2600 2601 // FIXME does mac not have cloexec for real or is this just a druntime problem????? 2602 version(Arsd_core_has_cloexec) { 2603 flags = O_CLOEXEC; 2604 } else { 2605 scope(success) 2606 setCloExec(this.handle); 2607 } 2608 2609 if(async) 2610 flags |= O_NONBLOCK; 2611 2612 final switch(mode) { 2613 case OpenMode.readOnly: 2614 flags |= O_RDONLY; 2615 break; 2616 case OpenMode.writeWithTruncation: 2617 flags |= O_WRONLY | O_TRUNC; 2618 2619 final switch(require) { 2620 case RequirePreexisting.no: 2621 flags |= O_CREAT; 2622 break; 2623 case RequirePreexisting.yes: 2624 break; 2625 } 2626 break; 2627 case OpenMode.appendOnly: 2628 flags |= O_APPEND; 2629 2630 final switch(require) { 2631 case RequirePreexisting.no: 2632 flags |= O_CREAT; 2633 break; 2634 case RequirePreexisting.yes: 2635 break; 2636 } 2637 break; 2638 case OpenMode.readAndWrite: 2639 flags |= O_RDWR; 2640 2641 final switch(require) { 2642 case RequirePreexisting.no: 2643 flags |= O_CREAT; 2644 break; 2645 case RequirePreexisting.yes: 2646 break; 2647 } 2648 break; 2649 } 2650 2651 auto perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; 2652 int fd = open(namez.ptr, flags, perms); 2653 if(fd == -1) { 2654 SavedArgument[3] args; 2655 args[0] = SavedArgument("filename", LimitedVariant(filename.path)); 2656 args[1] = SavedArgument("flags", LimitedVariant(flags, 2)); 2657 args[2] = SavedArgument("perms", LimitedVariant(perms, 8)); 2658 throw new ErrnoApiException("open", errno, args[]); 2659 } 2660 2661 this.handle = fd; 2662 } 2663 } 2664 2665 /++ 2666 2667 +/ 2668 private this(NativeFileHandle handleToWrap) { 2669 this.handle = handleToWrap; 2670 } 2671 2672 // only available on some types of file 2673 long size() { return 0; } 2674 2675 // note that there is no fsync thing, instead use the special flag. 2676 2677 /++ 2678 2679 +/ 2680 void close() { 2681 version(Windows) { 2682 Win32Enforce!CloseHandle(handle); 2683 handle = null; 2684 } else version(Posix) { 2685 import unix = core.sys.posix.unistd; 2686 import core.sys.posix.fcntl; 2687 2688 ErrnoEnforce!(unix.close)(handle); 2689 handle = -1; 2690 } 2691 } 2692 } 2693 2694 /++ 2695 2696 +/ 2697 version(HasFile) class File : AbstractFile { 2698 2699 /++ 2700 Opens a file in synchronous access mode. 2701 2702 The permission mask is on used on posix systems FIXME: implement it 2703 +/ 2704 this(FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0, uint permMask = 0) { 2705 super(false, filename, mode, require, specialFlags); 2706 } 2707 2708 /++ 2709 2710 +/ 2711 ubyte[] read(scope ubyte[] buffer) { 2712 return null; 2713 } 2714 2715 /++ 2716 2717 +/ 2718 void write(in void[] buffer) { 2719 } 2720 2721 enum Seek { 2722 current, 2723 fromBeginning, 2724 fromEnd 2725 } 2726 2727 // Seeking/telling/sizing is not permitted when appending and some files don't support it 2728 // also not permitted in async mode 2729 void seek(long where, Seek fromWhence) {} 2730 long tell() { return 0; } 2731 } 2732 2733 /++ 2734 Only one operation can be pending at any time in the current implementation. 2735 +/ 2736 version(HasFile) class AsyncFile : AbstractFile { 2737 /++ 2738 Opens a file in asynchronous access mode. 2739 +/ 2740 this(FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0, uint permissionMask = 0) { 2741 // FIXME: implement permissionMask 2742 super(true, filename, mode, require, specialFlags); 2743 } 2744 2745 package(arsd) this(NativeFileHandle adoptPreSetup) { 2746 super(adoptPreSetup); 2747 } 2748 2749 /// 2750 AsyncReadRequest read(ubyte[] buffer, long offset = 0) { 2751 return new AsyncReadRequest(this, buffer, offset); 2752 } 2753 2754 /// 2755 AsyncWriteRequest write(const(void)[] buffer, long offset = 0) { 2756 return new AsyncWriteRequest(this, cast(ubyte[]) buffer, offset); 2757 } 2758 2759 } 2760 2761 /++ 2762 Reads or writes a file in one call. It might internally yield, but is generally blocking if it returns values. The callback ones depend on the implementation. 2763 2764 Tip: prefer the callback ones. If settings where async is possible, it will do async, and if not, it will sync. 2765 2766 NOT IMPLEMENTED 2767 +/ 2768 void writeFile(string filename, const(void)[] contents) { 2769 2770 } 2771 2772 /// ditto 2773 string readTextFile(string filename, string fileEncoding = null) { 2774 return null; 2775 } 2776 2777 /// ditto 2778 const(ubyte[]) readBinaryFile(string filename) { 2779 return null; 2780 } 2781 2782 /+ 2783 private Class recycleObject(Class, Args...)(Class objectToRecycle, Args args) { 2784 if(objectToRecycle is null) 2785 return new Class(args); 2786 // destroy nulls out the vtable which is the first thing in the object 2787 // so if it hasn't already been destroyed, we'll do it here 2788 if((*cast(void**) objectToRecycle) !is null) { 2789 assert(typeid(objectToRecycle) is typeid(Class)); // to make sure we're actually recycling the right kind of object 2790 .destroy(objectToRecycle); 2791 } 2792 2793 // then go ahead and reinitialize it 2794 ubyte[] rawData = (cast(ubyte*) cast(void*) objectToRecycle)[0 .. __traits(classInstanceSize, Class)]; 2795 rawData[] = (cast(ubyte[]) typeid(Class).initializer)[]; 2796 2797 objectToRecycle.__ctor(args); 2798 2799 return objectToRecycle; 2800 } 2801 +/ 2802 2803 /+ 2804 /++ 2805 Preallocates a class object without initializing it. 2806 2807 This is suitable *only* for passing to one of the functions in here that takes a preallocated object for recycling. 2808 +/ 2809 Class preallocate(Class)() { 2810 import core.memory; 2811 // FIXME: can i pass NO_SCAN here? 2812 return cast(Class) GC.calloc(__traits(classInstanceSize, Class), 0, typeid(Class)); 2813 } 2814 2815 OwnedClass!Class preallocateOnStack(Class)() { 2816 2817 } 2818 +/ 2819 2820 // thanks for a random person on stack overflow for this function 2821 version(Windows) 2822 BOOL MyCreatePipeEx( 2823 PHANDLE lpReadPipe, 2824 PHANDLE lpWritePipe, 2825 LPSECURITY_ATTRIBUTES lpPipeAttributes, 2826 DWORD nSize, 2827 DWORD dwReadMode, 2828 DWORD dwWriteMode 2829 ) 2830 { 2831 HANDLE ReadPipeHandle, WritePipeHandle; 2832 DWORD dwError; 2833 CHAR[MAX_PATH] PipeNameBuffer; 2834 2835 if (nSize == 0) { 2836 nSize = 4096; 2837 } 2838 2839 // FIXME: should be atomic op and gshared 2840 static shared(int) PipeSerialNumber = 0; 2841 2842 import core.stdc.string; 2843 import core.stdc.stdio; 2844 2845 sprintf(PipeNameBuffer.ptr, 2846 "\\\\.\\Pipe\\ArsdCoreAnonymousPipe.%08x.%08x".ptr, 2847 GetCurrentProcessId(), 2848 atomicOp!"+="(PipeSerialNumber, 1) 2849 ); 2850 2851 ReadPipeHandle = CreateNamedPipeA( 2852 PipeNameBuffer.ptr, 2853 1/*PIPE_ACCESS_INBOUND*/ | dwReadMode, 2854 0/*PIPE_TYPE_BYTE*/ | 0/*PIPE_WAIT*/, 2855 1, // Number of pipes 2856 nSize, // Out buffer size 2857 nSize, // In buffer size 2858 120 * 1000, // Timeout in ms 2859 lpPipeAttributes 2860 ); 2861 2862 if (! ReadPipeHandle) { 2863 return FALSE; 2864 } 2865 2866 WritePipeHandle = CreateFileA( 2867 PipeNameBuffer.ptr, 2868 GENERIC_WRITE, 2869 0, // No sharing 2870 lpPipeAttributes, 2871 OPEN_EXISTING, 2872 FILE_ATTRIBUTE_NORMAL | dwWriteMode, 2873 null // Template file 2874 ); 2875 2876 if (INVALID_HANDLE_VALUE == WritePipeHandle) { 2877 dwError = GetLastError(); 2878 CloseHandle( ReadPipeHandle ); 2879 SetLastError(dwError); 2880 return FALSE; 2881 } 2882 2883 *lpReadPipe = ReadPipeHandle; 2884 *lpWritePipe = WritePipeHandle; 2885 return( TRUE ); 2886 } 2887 2888 2889 2890 /+ 2891 2892 // this is probably useless. 2893 2894 /++ 2895 Creates a pair of anonymous pipes ready for async operations. 2896 2897 You can pass some preallocated objects to recycle if you like. 2898 +/ 2899 AsyncAnonymousPipe[2] anonymousPipePair(AsyncAnonymousPipe[2] preallocatedObjects = [null, null], bool inheritable = false) { 2900 version(Posix) { 2901 int[2] fds; 2902 auto ret = pipe(fds); 2903 2904 if(ret == -1) 2905 throw new SystemApiException("pipe", errno); 2906 2907 // FIXME: do we want them inheritable? and do we want both sides to be async? 2908 if(!inheritable) { 2909 setCloExec(fds[0]); 2910 setCloExec(fds[1]); 2911 } 2912 // if it is inherited, do we actually want it non-blocking? 2913 makeNonBlocking(fds[0]); 2914 makeNonBlocking(fds[1]); 2915 2916 return [ 2917 recycleObject(preallocatedObjects[0], fds[0]), 2918 recycleObject(preallocatedObjects[1], fds[1]), 2919 ]; 2920 } else version(Windows) { 2921 HANDLE rp, wp; 2922 // FIXME: do we want them inheritable? and do we want both sides to be async? 2923 if(!MyCreatePipeEx(&rp, &wp, null, 0, FILE_FLAG_OVERLAPPED, FILE_FLAG_OVERLAPPED)) 2924 throw new SystemApiException("MyCreatePipeEx", GetLastError()); 2925 return [ 2926 recycleObject(preallocatedObjects[0], rp), 2927 recycleObject(preallocatedObjects[1], wp), 2928 ]; 2929 } else throw ArsdException!"NotYetImplemented"(); 2930 } 2931 // on posix, just do pipe() w/ non block 2932 // on windows, do an overlapped named pipe server, connect, stop listening, return pair. 2933 +/ 2934 2935 /+ 2936 class NamedPipe : AsyncFile { 2937 2938 } 2939 +/ 2940 2941 /++ 2942 A named pipe ready to accept connections. 2943 2944 A Windows named pipe is an IPC mechanism usable on local machines or across a Windows network. 2945 +/ 2946 version(Windows) 2947 class NamedPipeServer { 2948 // unix domain socket or windows named pipe 2949 2950 // Promise!AsyncAnonymousPipe connect; 2951 // Promise!AsyncAnonymousPipe accept; 2952 2953 // when a new connection arrives, it calls your callback 2954 // can be on a specific thread or on any thread 2955 } 2956 2957 private version(Windows) extern(Windows) { 2958 const(char)* inet_ntop(int, const void*, char*, socklen_t); 2959 } 2960 2961 /++ 2962 Some functions that return arrays allow you to provide your own buffer. These are indicated in the type system as `UserProvidedBuffer!Type`, and you get to decide what you want to happen if the buffer is too small via the [OnOutOfSpace] parameter. 2963 2964 These are usually optional, since an empty user provided buffer with the default policy of reallocate will also work fine for whatever needs to be returned, thanks to the garbage collector taking care of it for you. 2965 2966 The API inside `UserProvidedBuffer` is all private to the arsd library implementation; your job is just to provide the buffer to it with [provideBuffer] or a constructor call and decide on your on-out-of-space policy. 2967 2968 $(TIP 2969 To properly size a buffer, I suggest looking at what covers about 80% of cases. Trying to cover everything often leads to wasted buffer space, and if you use a reallocate policy it can cover the rest. You might be surprised how far just two elements can go! 2970 ) 2971 2972 History: 2973 Added August 4, 2023 (dub v11.0) 2974 +/ 2975 struct UserProvidedBuffer(T) { 2976 private T[] buffer; 2977 private int actualLength; 2978 private OnOutOfSpace policy; 2979 2980 /++ 2981 2982 +/ 2983 public this(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate) { 2984 this.buffer = buffer; 2985 this.policy = policy; 2986 } 2987 2988 package(arsd) bool append(T item) { 2989 if(actualLength < buffer.length) { 2990 buffer[actualLength++] = item; 2991 return true; 2992 } else final switch(policy) { 2993 case OnOutOfSpace.discard: 2994 return false; 2995 case OnOutOfSpace.exception: 2996 throw ArsdException!"Buffer out of space"(buffer.length, actualLength); 2997 case OnOutOfSpace.reallocate: 2998 buffer ~= item; 2999 actualLength++; 3000 return true; 3001 } 3002 } 3003 3004 package(arsd) T[] slice() return { 3005 return buffer[0 .. actualLength]; 3006 } 3007 } 3008 3009 /// ditto 3010 UserProvidedBuffer!T provideBuffer(T)(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate) { 3011 return UserProvidedBuffer!T(buffer, policy); 3012 } 3013 3014 /++ 3015 Possible policies for [UserProvidedBuffer]s that run out of space. 3016 +/ 3017 enum OnOutOfSpace { 3018 reallocate, /// reallocate the buffer with the GC to make room 3019 discard, /// discard all contents that do not fit in your provided buffer 3020 exception, /// throw an exception if there is data that would not fit in your provided buffer 3021 } 3022 3023 3024 /++ 3025 For functions that give you an unknown address, you can use this to hold it. 3026 3027 Can get: 3028 ip4 3029 ip6 3030 unix 3031 abstract_ 3032 3033 name lookup for connect (stream or dgram) 3034 request canonical name? 3035 3036 interface lookup for bind (stream or dgram) 3037 +/ 3038 version(HasSocket) struct SocketAddress { 3039 import core.sys.posix.netdb; 3040 3041 /++ 3042 Provides the set of addresses to listen on all supported protocols on the machine for the given interfaces. `localhost` only listens on the loopback interface, whereas `allInterfaces` will listen on loopback as well as the others on the system (meaning it may be publicly exposed to the internet). 3043 3044 If you provide a buffer, I recommend using one of length two, so `SocketAddress[2]`, since this usually provides one address for ipv4 and one for ipv6. 3045 +/ 3046 static SocketAddress[] localhost(ushort port, return UserProvidedBuffer!SocketAddress buffer = null) { 3047 buffer.append(ip6("::1", port)); 3048 buffer.append(ip4("127.0.0.1", port)); 3049 return buffer.slice; 3050 } 3051 3052 /// ditto 3053 static SocketAddress[] allInterfaces(ushort port, return UserProvidedBuffer!SocketAddress buffer = null) { 3054 char[16] str; 3055 return allInterfaces(intToString(port, str[]), buffer); 3056 } 3057 3058 /// ditto 3059 static SocketAddress[] allInterfaces(scope const char[] serviceOrPort, return UserProvidedBuffer!SocketAddress buffer = null) { 3060 addrinfo hints; 3061 hints.ai_flags = AI_PASSIVE; 3062 hints.ai_socktype = SOCK_STREAM; // just to filter it down a little tbh 3063 return get(null, serviceOrPort, &hints, buffer); 3064 } 3065 3066 /++ 3067 Returns a single address object for the given protocol and parameters. 3068 3069 You probably should generally prefer [get], [localhost], or [allInterfaces] to have more flexible code. 3070 +/ 3071 static SocketAddress ip4(scope const char[] address, ushort port, bool forListening = false) { 3072 return getSingleAddress(AF_INET, AI_NUMERICHOST | (forListening ? AI_PASSIVE : 0), address, port); 3073 } 3074 3075 /// ditto 3076 static SocketAddress ip4(ushort port) { 3077 return ip4(null, port, true); 3078 } 3079 3080 /// ditto 3081 static SocketAddress ip6(scope const char[] address, ushort port, bool forListening = false) { 3082 return getSingleAddress(AF_INET6, AI_NUMERICHOST | (forListening ? AI_PASSIVE : 0), address, port); 3083 } 3084 3085 /// ditto 3086 static SocketAddress ip6(ushort port) { 3087 return ip6(null, port, true); 3088 } 3089 3090 /// ditto 3091 static SocketAddress unix(scope const char[] path) { 3092 // FIXME 3093 SocketAddress addr; 3094 return addr; 3095 } 3096 3097 /// ditto 3098 static SocketAddress abstract_(scope const char[] path) { 3099 char[190] buffer = void; 3100 buffer[0] = 0; 3101 buffer[1 .. path.length] = path[]; 3102 return unix(buffer[0 .. 1 + path.length]); 3103 } 3104 3105 private static SocketAddress getSingleAddress(int family, int flags, scope const char[] address, ushort port) { 3106 addrinfo hints; 3107 hints.ai_family = family; 3108 hints.ai_flags = flags; 3109 3110 char[16] portBuffer; 3111 char[] portString = intToString(port, portBuffer[]); 3112 3113 SocketAddress[1] addr; 3114 auto res = get(address, portString, &hints, provideBuffer(addr[])); 3115 if(res.length == 0) 3116 throw ArsdException!"bad address"(address.idup, port); 3117 return res[0]; 3118 } 3119 3120 /++ 3121 Calls `getaddrinfo` and returns the array of results. It will populate the data into the buffer you provide, if you provide one, otherwise it will allocate its own. 3122 +/ 3123 static SocketAddress[] get(scope const char[] nodeName, scope const char[] serviceOrPort, addrinfo* hints = null, return UserProvidedBuffer!SocketAddress buffer = null, scope bool delegate(scope addrinfo* ai) filter = null) @trusted { 3124 addrinfo* res; 3125 CharzBuffer node = nodeName; 3126 CharzBuffer service = serviceOrPort; 3127 auto ret = getaddrinfo(nodeName is null ? null : node.ptr, serviceOrPort is null ? null : service.ptr, hints, &res); 3128 if(ret == 0) { 3129 auto current = res; 3130 while(current) { 3131 if(filter is null || filter(current)) { 3132 SocketAddress addr; 3133 addr.addrlen = cast(socklen_t) current.ai_addrlen; 3134 switch(current.ai_family) { 3135 case AF_INET: 3136 addr.in4 = * cast(sockaddr_in*) current.ai_addr; 3137 break; 3138 case AF_INET6: 3139 addr.in6 = * cast(sockaddr_in6*) current.ai_addr; 3140 break; 3141 case AF_UNIX: 3142 addr.unix_address = * cast(sockaddr_un*) current.ai_addr; 3143 break; 3144 default: 3145 // skip 3146 } 3147 3148 if(!buffer.append(addr)) 3149 break; 3150 } 3151 3152 current = current.ai_next; 3153 } 3154 3155 freeaddrinfo(res); 3156 } else { 3157 version(Windows) { 3158 throw new WindowsApiException("getaddrinfo", ret); 3159 } else { 3160 const char* error = gai_strerror(ret); 3161 } 3162 } 3163 3164 return buffer.slice; 3165 } 3166 3167 /++ 3168 Returns a string representation of the address that identifies it in a custom format. 3169 3170 $(LIST 3171 * Unix domain socket addresses are their path prefixed with "unix:", unless they are in the abstract namespace, in which case it is prefixed with "abstract:" and the zero is trimmed out. For example, "unix:/tmp/pipe". 3172 3173 * IPv4 addresses are written in dotted decimal followed by a colon and the port number. For example, "127.0.0.1:8080". 3174 3175 * IPv6 addresses are written in colon separated hex format, but enclosed in brackets, then followed by the colon and port number. For example, "[::1]:8080". 3176 ) 3177 +/ 3178 string toString() const @trusted { 3179 char[200] buffer; 3180 switch(address.sa_family) { 3181 case AF_INET: 3182 auto writable = stringz(inet_ntop(address.sa_family, &in4.sin_addr, buffer.ptr, buffer.length)); 3183 auto it = writable.borrow; 3184 buffer[it.length] = ':'; 3185 auto numbers = intToString(port, buffer[it.length + 1 .. $]); 3186 return buffer[0 .. it.length + 1 + numbers.length].idup; 3187 case AF_INET6: 3188 buffer[0] = '['; 3189 auto writable = stringz(inet_ntop(address.sa_family, &in6.sin6_addr, buffer.ptr + 1, buffer.length - 1)); 3190 auto it = writable.borrow; 3191 buffer[it.length + 1] = ']'; 3192 buffer[it.length + 2] = ':'; 3193 auto numbers = intToString(port, buffer[it.length + 3 .. $]); 3194 return buffer[0 .. it.length + 3 + numbers.length].idup; 3195 case AF_UNIX: 3196 // FIXME: it might be abstract in which case stringz is wrong!!!!! 3197 auto writable = stringz(cast(char*) unix_address.sun_path.ptr).borrow; 3198 if(writable.length == 0) 3199 return "unix:"; 3200 string prefix = writable[0] == 0 ? "abstract:" : "unix:"; 3201 buffer[0 .. prefix.length] = prefix[]; 3202 buffer[prefix.length .. prefix.length + writable.length] = writable[writable[0] == 0 ? 1 : 0 .. $]; 3203 return buffer.idup; 3204 case AF_UNSPEC: 3205 return "<unspecified address>"; 3206 default: 3207 return "<unsupported address>"; // FIXME 3208 } 3209 } 3210 3211 ushort port() const @trusted { 3212 switch(address.sa_family) { 3213 case AF_INET: 3214 return ntohs(in4.sin_port); 3215 case AF_INET6: 3216 return ntohs(in6.sin6_port); 3217 default: 3218 return 0; 3219 } 3220 } 3221 3222 /+ 3223 @safe unittest { 3224 SocketAddress[4] buffer; 3225 foreach(addr; SocketAddress.get("arsdnet.net", "http", null, provideBuffer(buffer[]))) 3226 writeln(addr.toString()); 3227 } 3228 +/ 3229 3230 /+ 3231 unittest { 3232 // writeln(SocketAddress.ip4(null, 4444, true)); 3233 // writeln(SocketAddress.ip4("400.3.2.1", 4444)); 3234 // writeln(SocketAddress.ip4("bar", 4444)); 3235 foreach(addr; localhost(4444)) 3236 writeln(addr.toString()); 3237 } 3238 +/ 3239 3240 socklen_t addrlen = typeof(this).sizeof - socklen_t.sizeof; // the size of the union below 3241 3242 union { 3243 sockaddr address; 3244 3245 sockaddr_storage storage; 3246 3247 sockaddr_in in4; 3248 sockaddr_in6 in6; 3249 3250 sockaddr_un unix_address; 3251 } 3252 3253 /+ 3254 this(string node, string serviceOrPort, int family = 0) { 3255 // need to populate the approrpiate address and the length and make sure you set sa_family 3256 } 3257 +/ 3258 3259 int domain() { 3260 return address.sa_family; 3261 } 3262 sockaddr* rawAddr() return { 3263 return &address; 3264 } 3265 socklen_t rawAddrLength() { 3266 return addrlen; 3267 } 3268 3269 // FIXME it is AF_BLUETOOTH 3270 // see: https://people.csail.mit.edu/albert/bluez-intro/x79.html 3271 // see: https://learn.microsoft.com/en-us/windows/win32/Bluetooth/bluetooth-programming-with-windows-sockets 3272 } 3273 3274 private version(Windows) { 3275 struct sockaddr_un { 3276 ushort sun_family; 3277 char[108] sun_path; 3278 } 3279 } 3280 3281 version(HasFile) class AsyncSocket : AsyncFile { 3282 // otherwise: accept, bind, connect, shutdown, close. 3283 3284 static auto lastError() { 3285 version(Windows) 3286 return WSAGetLastError(); 3287 else 3288 return errno; 3289 } 3290 3291 static bool wouldHaveBlocked() { 3292 auto error = lastError; 3293 version(Windows) { 3294 return error == WSAEWOULDBLOCK || error == WSAETIMEDOUT; 3295 } else { 3296 return error == EAGAIN || error == EWOULDBLOCK; 3297 } 3298 } 3299 3300 version(Windows) 3301 enum INVALID = INVALID_SOCKET; 3302 else 3303 enum INVALID = -1; 3304 3305 // type is mostly SOCK_STREAM or SOCK_DGRAM 3306 /++ 3307 Creates a socket compatible with the given address. It does not actually connect or bind, nor store the address. You will want to pass it again to those functions: 3308 3309 --- 3310 auto socket = new Socket(address, Socket.Type.Stream); 3311 socket.connect(address).waitForCompletion(); 3312 --- 3313 +/ 3314 this(SocketAddress address, int type, int protocol = 0) { 3315 // need to look up these values for linux 3316 // type |= SOCK_NONBLOCK | SOCK_CLOEXEC; 3317 3318 handle_ = socket(address.domain(), type, protocol); 3319 if(handle == INVALID) 3320 throw new SystemApiException("socket", lastError()); 3321 3322 super(cast(NativeFileHandle) handle); // I think that cast is ok on Windows... i think 3323 3324 version(Posix) { 3325 makeNonBlocking(handle); 3326 setCloExec(handle); 3327 } 3328 3329 if(address.domain == AF_INET6) { 3330 int opt = 1; 3331 setsockopt(handle, IPPROTO_IPV6 /*SOL_IPV6*/, IPV6_V6ONLY, &opt, opt.sizeof); 3332 } 3333 3334 // FIXME: chekc for broadcast 3335 3336 // FIXME: REUSEADDR ? 3337 3338 // FIXME: also set NO_DELAY prolly 3339 // int opt = 1; 3340 // setsockopt(handle, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof); 3341 } 3342 3343 /++ 3344 Enabling NODELAY can give latency improvements if you are managing buffers on your end 3345 +/ 3346 void setNoDelay(bool enabled) { 3347 3348 } 3349 3350 /++ 3351 3352 `allowQuickRestart` will set the SO_REUSEADDR on unix and SO_DONTLINGER on Windows, 3353 allowing the application to be quickly restarted despite there still potentially being 3354 pending data in the tcp stack. 3355 3356 See https://stackoverflow.com/questions/3229860/what-is-the-meaning-of-so-reuseaddr-setsockopt-option-linux for more information. 3357 3358 If you already set your appropriate socket options or value correctness and reliability of the network stream over restart speed, leave this at the default `false`. 3359 +/ 3360 void bind(SocketAddress address, bool allowQuickRestart = false) { 3361 if(allowQuickRestart) { 3362 // FIXME 3363 } 3364 3365 auto ret = .bind(handle, address.rawAddr, address.rawAddrLength); 3366 if(ret == -1) 3367 throw new SystemApiException("bind", lastError); 3368 } 3369 3370 /++ 3371 You must call [bind] before this. 3372 3373 The backlog should be set to a value where your application can reliably catch up on the backlog in a reasonable amount of time under average load. It is meant to smooth over short duration bursts and making it too big will leave clients hanging - which might cause them to try to reconnect, thinking things got lost in transit, adding to your impossible backlog. 3374 3375 I personally tend to set this to be two per worker thread unless I have actual real world measurements saying to do something else. It is a bit arbitrary and not based on legitimate reasoning, it just seems to work for me (perhaps just because it has never really been put to the test). 3376 +/ 3377 void listen(int backlog) { 3378 auto ret = .listen(handle, backlog); 3379 if(ret == -1) 3380 throw new SystemApiException("listen", lastError); 3381 } 3382 3383 /++ 3384 +/ 3385 void shutdown(int how) { 3386 auto ret = .shutdown(handle, how); 3387 if(ret == -1) 3388 throw new SystemApiException("shutdown", lastError); 3389 } 3390 3391 /++ 3392 +/ 3393 override void close() { 3394 version(Windows) 3395 closesocket(handle); 3396 else 3397 .close(handle); 3398 handle_ = -1; 3399 } 3400 3401 /++ 3402 You can also construct your own request externally to control the memory more. 3403 +/ 3404 AsyncConnectRequest connect(SocketAddress address, ubyte[] bufferToSend = null) { 3405 return new AsyncConnectRequest(this, address, bufferToSend); 3406 } 3407 3408 /++ 3409 You can also construct your own request externally to control the memory more. 3410 +/ 3411 AsyncAcceptRequest accept() { 3412 return new AsyncAcceptRequest(this); 3413 } 3414 3415 // note that send is just sendto w/ a null address 3416 // and receive is just receivefrom w/ a null address 3417 /++ 3418 You can also construct your own request externally to control the memory more. 3419 +/ 3420 AsyncSendRequest send(const(ubyte)[] buffer, int flags = 0) { 3421 return new AsyncSendRequest(this, buffer, null, flags); 3422 } 3423 3424 /++ 3425 You can also construct your own request externally to control the memory more. 3426 +/ 3427 AsyncReceiveRequest receive(ubyte[] buffer, int flags = 0) { 3428 return new AsyncReceiveRequest(this, buffer, null, flags); 3429 } 3430 3431 /++ 3432 You can also construct your own request externally to control the memory more. 3433 +/ 3434 AsyncSendRequest sendTo(const(ubyte)[] buffer, SocketAddress* address, int flags = 0) { 3435 return new AsyncSendRequest(this, buffer, address, flags); 3436 } 3437 /++ 3438 You can also construct your own request externally to control the memory more. 3439 +/ 3440 AsyncReceiveRequest receiveFrom(ubyte[] buffer, SocketAddress* address, int flags = 0) { 3441 return new AsyncReceiveRequest(this, buffer, address, flags); 3442 } 3443 3444 /++ 3445 +/ 3446 SocketAddress localAddress() { 3447 SocketAddress addr; 3448 getsockname(handle, &addr.address, &addr.addrlen); 3449 return addr; 3450 } 3451 /++ 3452 +/ 3453 SocketAddress peerAddress() { 3454 SocketAddress addr; 3455 getpeername(handle, &addr.address, &addr.addrlen); 3456 return addr; 3457 } 3458 3459 // for unix sockets on unix only: send/receive fd, get peer creds 3460 3461 /++ 3462 3463 +/ 3464 final NativeSocketHandle handle() { 3465 return handle_; 3466 } 3467 3468 private NativeSocketHandle handle_; 3469 } 3470 3471 /++ 3472 Initiates a connection request and optionally sends initial data as soon as possible. 3473 3474 Calls `ConnectEx` on Windows and emulates it on other systems. 3475 3476 The entire buffer is sent before the operation is considered complete. 3477 3478 NOT IMPLEMENTED / NOT STABLE 3479 +/ 3480 version(HasSocket) class AsyncConnectRequest : AsyncOperationRequest { 3481 // FIXME: i should take a list of addresses and take the first one that succeeds, so a getaddrinfo can be sent straight in. 3482 this(AsyncSocket socket, SocketAddress address, ubyte[] dataToWrite) { 3483 3484 } 3485 3486 override void start() {} 3487 override void cancel() {} 3488 override bool isComplete() { return true; } 3489 override AsyncConnectResponse waitForCompletion() { assert(0); } 3490 } 3491 /++ 3492 +/ 3493 version(HasSocket) class AsyncConnectResponse : AsyncOperationResponse { 3494 const SystemErrorCode errorCode; 3495 3496 this(SystemErrorCode errorCode) { 3497 this.errorCode = errorCode; 3498 } 3499 3500 override bool wasSuccessful() { 3501 return errorCode.wasSuccessful; 3502 } 3503 3504 } 3505 3506 // FIXME: TransmitFile/sendfile support 3507 3508 /++ 3509 Calls `AcceptEx` on Windows and emulates it on other systems. 3510 3511 NOT IMPLEMENTED / NOT STABLE 3512 +/ 3513 version(HasSocket) class AsyncAcceptRequest : AsyncOperationRequest { 3514 AsyncSocket socket; 3515 3516 override void start() {} 3517 override void cancel() {} 3518 override bool isComplete() { return true; } 3519 override AsyncConnectResponse waitForCompletion() { assert(0); } 3520 3521 3522 struct LowLevelOperation { 3523 AsyncSocket file; 3524 ubyte[] buffer; 3525 SocketAddress* address; 3526 3527 this(typeof(this.tupleof) args) { 3528 this.tupleof = args; 3529 } 3530 3531 version(Windows) { 3532 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 3533 WSABUF buf; 3534 buf.len = cast(int) buffer.length; 3535 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 3536 3537 uint flags; 3538 3539 if(address is null) 3540 return WSARecv(file.handle, &buf, 1, null, &flags, overlapped, ocr); 3541 else { 3542 return WSARecvFrom(file.handle, &buf, 1, null, &flags, &(address.address), &(address.addrlen), overlapped, ocr); 3543 } 3544 } 3545 } else { 3546 auto opCall() { 3547 int flags; 3548 if(address is null) 3549 return core.sys.posix.sys.socket.recv(file.handle, buffer.ptr, buffer.length, flags); 3550 else 3551 return core.sys.posix.sys.socket.recvfrom(file.handle, buffer.ptr, buffer.length, flags, &(address.address), &(address.addrlen)); 3552 } 3553 } 3554 3555 string errorString() { 3556 return "Receive"; 3557 } 3558 } 3559 mixin OverlappedIoRequest!(AsyncAcceptResponse, LowLevelOperation); 3560 3561 this(AsyncSocket socket, ubyte[] buffer = null, SocketAddress* address = null) { 3562 llo = LowLevelOperation(socket, buffer, address); 3563 this.response = typeof(this.response).defaultConstructed; 3564 } 3565 3566 // can also look up the local address 3567 } 3568 /++ 3569 +/ 3570 version(HasSocket) class AsyncAcceptResponse : AsyncOperationResponse { 3571 AsyncSocket newSocket; 3572 const SystemErrorCode errorCode; 3573 3574 this(SystemErrorCode errorCode, ubyte[] buffer) { 3575 this.errorCode = errorCode; 3576 } 3577 3578 this(AsyncSocket newSocket, SystemErrorCode errorCode) { 3579 this.newSocket = newSocket; 3580 this.errorCode = errorCode; 3581 } 3582 3583 override bool wasSuccessful() { 3584 return errorCode.wasSuccessful; 3585 } 3586 } 3587 3588 /++ 3589 +/ 3590 version(HasSocket) class AsyncReceiveRequest : AsyncOperationRequest { 3591 struct LowLevelOperation { 3592 AsyncSocket file; 3593 ubyte[] buffer; 3594 int flags; 3595 SocketAddress* address; 3596 3597 this(typeof(this.tupleof) args) { 3598 this.tupleof = args; 3599 } 3600 3601 version(Windows) { 3602 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 3603 WSABUF buf; 3604 buf.len = cast(int) buffer.length; 3605 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 3606 3607 uint flags = this.flags; 3608 3609 if(address is null) 3610 return WSARecv(file.handle, &buf, 1, null, &flags, overlapped, ocr); 3611 else { 3612 return WSARecvFrom(file.handle, &buf, 1, null, &flags, &(address.address), &(address.addrlen), overlapped, ocr); 3613 } 3614 } 3615 } else { 3616 auto opCall() { 3617 if(address is null) 3618 return core.sys.posix.sys.socket.recv(file.handle, buffer.ptr, buffer.length, flags); 3619 else 3620 return core.sys.posix.sys.socket.recvfrom(file.handle, buffer.ptr, buffer.length, flags, &(address.address), &(address.addrlen)); 3621 } 3622 } 3623 3624 string errorString() { 3625 return "Receive"; 3626 } 3627 } 3628 mixin OverlappedIoRequest!(AsyncReceiveResponse, LowLevelOperation); 3629 3630 this(AsyncSocket socket, ubyte[] buffer, SocketAddress* address, int flags) { 3631 llo = LowLevelOperation(socket, buffer, flags, address); 3632 this.response = typeof(this.response).defaultConstructed; 3633 } 3634 3635 } 3636 /++ 3637 +/ 3638 version(HasSocket) class AsyncReceiveResponse : AsyncOperationResponse { 3639 const ubyte[] bufferWritten; 3640 const SystemErrorCode errorCode; 3641 3642 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 3643 this.errorCode = errorCode; 3644 this.bufferWritten = bufferWritten; 3645 } 3646 3647 override bool wasSuccessful() { 3648 return errorCode.wasSuccessful; 3649 } 3650 } 3651 3652 /++ 3653 +/ 3654 version(HasSocket) class AsyncSendRequest : AsyncOperationRequest { 3655 struct LowLevelOperation { 3656 AsyncSocket file; 3657 const(ubyte)[] buffer; 3658 int flags; 3659 SocketAddress* address; 3660 3661 this(typeof(this.tupleof) args) { 3662 this.tupleof = args; 3663 } 3664 3665 version(Windows) { 3666 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 3667 WSABUF buf; 3668 buf.len = cast(int) buffer.length; 3669 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 3670 3671 if(address is null) 3672 return WSASend(file.handle, &buf, 1, null, flags, overlapped, ocr); 3673 else { 3674 return WSASendTo(file.handle, &buf, 1, null, flags, address.rawAddr, address.rawAddrLength, overlapped, ocr); 3675 } 3676 } 3677 } else { 3678 auto opCall() { 3679 if(address is null) 3680 return core.sys.posix.sys.socket.send(file.handle, buffer.ptr, buffer.length, flags); 3681 else 3682 return core.sys.posix.sys.socket.sendto(file.handle, buffer.ptr, buffer.length, flags, address.rawAddr, address.rawAddrLength); 3683 } 3684 } 3685 3686 string errorString() { 3687 return "Send"; 3688 } 3689 } 3690 mixin OverlappedIoRequest!(AsyncSendResponse, LowLevelOperation); 3691 3692 this(AsyncSocket socket, const(ubyte)[] buffer, SocketAddress* address, int flags) { 3693 llo = LowLevelOperation(socket, buffer, flags, address); 3694 this.response = typeof(this.response).defaultConstructed; 3695 } 3696 } 3697 3698 /++ 3699 +/ 3700 version(HasSocket) class AsyncSendResponse : AsyncOperationResponse { 3701 const ubyte[] bufferWritten; 3702 const SystemErrorCode errorCode; 3703 3704 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 3705 this.errorCode = errorCode; 3706 this.bufferWritten = bufferWritten; 3707 } 3708 3709 override bool wasSuccessful() { 3710 return errorCode.wasSuccessful; 3711 } 3712 3713 } 3714 3715 /++ 3716 A set of sockets bound and ready to accept connections on worker threads. 3717 3718 Depending on the specified address, it can be tcp, tcpv6, unix domain, or all of the above. 3719 3720 NOT IMPLEMENTED / NOT STABLE 3721 +/ 3722 version(HasSocket) class StreamServer { 3723 AsyncSocket[] sockets; 3724 3725 this(SocketAddress[] listenTo, int backlog = 8) { 3726 foreach(listen; listenTo) { 3727 auto socket = new AsyncSocket(listen, SOCK_STREAM); 3728 3729 // FIXME: allInterfaces for ipv6 also covers ipv4 so the bind can fail... 3730 // so we have to permit it to fail w/ address in use if we know we already 3731 // are listening to ipv6 3732 3733 // or there is a setsockopt ipv6 only thing i could set. 3734 3735 socket.bind(listen); 3736 socket.listen(backlog); 3737 sockets ~= socket; 3738 3739 // writeln(socket.localAddress.port); 3740 } 3741 3742 // i have to start accepting on each thread for each socket... 3743 } 3744 // when a new connection arrives, it calls your callback 3745 // can be on a specific thread or on any thread 3746 3747 3748 void start() { 3749 foreach(socket; sockets) { 3750 auto request = socket.accept(); 3751 request.start(); 3752 } 3753 } 3754 } 3755 3756 /+ 3757 unittest { 3758 auto ss = new StreamServer(SocketAddress.localhost(0)); 3759 } 3760 +/ 3761 3762 /++ 3763 A socket bound and ready to use receiveFrom 3764 3765 Depending on the address, it can be udp or unix domain. 3766 3767 NOT IMPLEMENTED / NOT STABLE 3768 +/ 3769 version(HasSocket) class DatagramListener { 3770 // whenever a udp message arrives, it calls your callback 3771 // can be on a specific thread or on any thread 3772 3773 // UDP is realistically just an async read on the bound socket 3774 // just it can get the "from" data out and might need the "more in packet" flag 3775 } 3776 3777 /++ 3778 Just in case I decide to change the implementation some day. 3779 +/ 3780 version(HasFile) alias AsyncAnonymousPipe = AsyncFile; 3781 3782 3783 // AsyncAnonymousPipe connectNamedPipe(AsyncAnonymousPipe preallocated, string name) 3784 3785 // unix fifos are considered just non-seekable files and have no special support in the lib; open them as a regular file w/ the async flag. 3786 3787 // DIRECTORY LISTINGS 3788 // not async, so if you want that, do it in a helper thread 3789 // just a convenient function to have (tho phobos has a decent one too, importing it expensive af) 3790 3791 /++ 3792 Note that the order of items called for your delegate is undefined; if you want it sorted, you'll have to collect and sort yourself. But it *might* be sorted by the OS (on Windows, it almost always is), so consider that when choosing a sorting algorithm. 3793 3794 History: 3795 previously in minigui as a private function. Moved to arsd.core on April 3, 2023 3796 +/ 3797 version(HasFile) GetFilesResult getFiles(string directory, scope void delegate(string name, bool isDirectory) dg) { 3798 // FIXME: my buffers here aren't great lol 3799 3800 SavedArgument[1] argsForException() { 3801 return [ 3802 SavedArgument("directory", LimitedVariant(directory)), 3803 ]; 3804 } 3805 3806 version(Windows) { 3807 WIN32_FIND_DATA data; 3808 // FIXME: if directory ends with / or \\ ? 3809 WCharzBuffer search = WCharzBuffer(directory ~ "/*"); 3810 auto handle = FindFirstFileW(search.ptr, &data); 3811 scope(exit) if(handle !is INVALID_HANDLE_VALUE) FindClose(handle); 3812 if(handle is INVALID_HANDLE_VALUE) { 3813 if(GetLastError() == ERROR_FILE_NOT_FOUND) 3814 return GetFilesResult.fileNotFound; 3815 throw new WindowsApiException("FindFirstFileW", GetLastError(), argsForException()[]); 3816 } 3817 3818 try_more: 3819 3820 string name = makeUtf8StringFromWindowsString(data.cFileName[0 .. findIndexOfZero(data.cFileName[])]); 3821 3822 dg(name, (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false); 3823 3824 auto ret = FindNextFileW(handle, &data); 3825 if(ret == 0) { 3826 if(GetLastError() == ERROR_NO_MORE_FILES) 3827 return GetFilesResult.success; 3828 throw new WindowsApiException("FindNextFileW", GetLastError(), argsForException()[]); 3829 } 3830 3831 goto try_more; 3832 3833 } else version(Posix) { 3834 import core.sys.posix.dirent; 3835 import core.stdc.errno; 3836 auto dir = opendir((directory ~ "\0").ptr); 3837 scope(exit) 3838 if(dir) closedir(dir); 3839 if(dir is null) 3840 throw new ErrnoApiException("opendir", errno, argsForException()); 3841 3842 auto dirent = readdir(dir); 3843 if(dirent is null) 3844 return GetFilesResult.fileNotFound; 3845 3846 try_more: 3847 3848 string name = dirent.d_name[0 .. findIndexOfZero(dirent.d_name[])].idup; 3849 3850 dg(name, dirent.d_type == DT_DIR); 3851 3852 dirent = readdir(dir); 3853 if(dirent is null) 3854 return GetFilesResult.success; 3855 3856 goto try_more; 3857 } else static assert(0); 3858 } 3859 3860 /// ditto 3861 enum GetFilesResult { 3862 success, 3863 fileNotFound 3864 } 3865 3866 /++ 3867 This is currently a simplified glob where only the * wildcard in the first or last position gets special treatment or a single * in the middle. 3868 3869 More things may be added later to be more like what Phobos supports. 3870 +/ 3871 bool matchesFilePattern(scope const(char)[] name, scope const(char)[] pattern) { 3872 if(pattern.length == 0) 3873 return false; 3874 if(pattern == "*") 3875 return true; 3876 if(pattern.length > 2 && pattern[0] == '*' && pattern[$-1] == '*') { 3877 // if the rest of pattern appears in name, it is good 3878 return name.indexOf(pattern[1 .. $-1]) != -1; 3879 } else if(pattern[0] == '*') { 3880 // if the rest of pattern is at end of name, it is good 3881 return name.endsWith(pattern[1 .. $]); 3882 } else if(pattern[$-1] == '*') { 3883 // if the rest of pattern is at start of name, it is good 3884 return name.startsWith(pattern[0 .. $-1]); 3885 } else if(pattern.length >= 3) { 3886 auto idx = pattern.indexOf("*"); 3887 if(idx != -1) { 3888 auto lhs = pattern[0 .. idx]; 3889 auto rhs = pattern[idx + 1 .. $]; 3890 if(name.length >= lhs.length + rhs.length) { 3891 return name.startsWith(lhs) && name.endsWith(rhs); 3892 } else { 3893 return false; 3894 } 3895 } 3896 } 3897 3898 return name == pattern; 3899 } 3900 3901 unittest { 3902 assert("test.html".matchesFilePattern("*")); 3903 assert("test.html".matchesFilePattern("*.html")); 3904 assert("test.html".matchesFilePattern("*.*")); 3905 assert("test.html".matchesFilePattern("test.*")); 3906 assert(!"test.html".matchesFilePattern("pest.*")); 3907 assert(!"test.html".matchesFilePattern("*.dhtml")); 3908 3909 assert("test.html".matchesFilePattern("t*.html")); 3910 assert(!"test.html".matchesFilePattern("e*.html")); 3911 } 3912 3913 package(arsd) int indexOf(scope const(char)[] haystack, scope const(char)[] needle) { 3914 if(haystack.length < needle.length) 3915 return -1; 3916 if(haystack == needle) 3917 return 0; 3918 foreach(i; 0 .. haystack.length - needle.length + 1) 3919 if(haystack[i .. i + needle.length] == needle) 3920 return cast(int) i; 3921 return -1; 3922 } 3923 3924 unittest { 3925 assert("foo".indexOf("f") == 0); 3926 assert("foo".indexOf("o") == 1); 3927 assert("foo".indexOf("foo") == 0); 3928 assert("foo".indexOf("oo") == 1); 3929 assert("foo".indexOf("fo") == 0); 3930 assert("foo".indexOf("boo") == -1); 3931 assert("foo".indexOf("food") == -1); 3932 } 3933 3934 package(arsd) bool endsWith(scope const(char)[] haystack, scope const(char)[] needle) { 3935 if(needle.length > haystack.length) 3936 return false; 3937 return haystack[$ - needle.length .. $] == needle; 3938 } 3939 3940 unittest { 3941 assert("foo".endsWith("o")); 3942 assert("foo".endsWith("oo")); 3943 assert("foo".endsWith("foo")); 3944 assert(!"foo".endsWith("food")); 3945 assert(!"foo".endsWith("d")); 3946 } 3947 3948 package(arsd) bool startsWith(scope const(char)[] haystack, scope const(char)[] needle) { 3949 if(needle.length > haystack.length) 3950 return false; 3951 return haystack[0 .. needle.length] == needle; 3952 } 3953 3954 unittest { 3955 assert("foo".startsWith("f")); 3956 assert("foo".startsWith("fo")); 3957 assert("foo".startsWith("foo")); 3958 assert(!"foo".startsWith("food")); 3959 assert(!"foo".startsWith("d")); 3960 } 3961 3962 3963 // FILE/DIR WATCHES 3964 // linux does it by name, windows and bsd do it by handle/descriptor 3965 // dispatches change event to either your thread or maybe the any task` queue. 3966 3967 /++ 3968 PARTIALLY IMPLEMENTED / NOT STABLE 3969 3970 +/ 3971 class DirectoryWatcher { 3972 private { 3973 version(Arsd_core_windows) { 3974 OVERLAPPED overlapped; 3975 HANDLE hDirectory; 3976 ubyte[] buffer; 3977 3978 extern(Windows) 3979 static void overlappedCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped) { 3980 typeof(this) rr = cast(typeof(this)) (cast(void*) lpOverlapped - typeof(this).overlapped.offsetof); 3981 3982 // dwErrorCode 3983 auto response = rr.buffer[0 .. dwNumberOfBytesTransferred]; 3984 3985 while(response.length) { 3986 auto fni = cast(FILE_NOTIFY_INFORMATION*) response.ptr; 3987 auto filename = fni.FileName[0 .. fni.FileNameLength]; 3988 3989 if(fni.NextEntryOffset) 3990 response = response[fni.NextEntryOffset .. $]; 3991 else 3992 response = response[$..$]; 3993 3994 // FIXME: I think I need to pin every overlapped op while it is pending 3995 // and unpin it when it is returned. GC.addRoot... but i don't wanna do that 3996 // every op so i guess i should do a refcount scheme similar to the other callback helper. 3997 3998 rr.changeHandler( 3999 FilePath(makeUtf8StringFromWindowsString(filename)), // FIXME: this is a relative path 4000 ChangeOperation.unknown // FIXME this is fni.Action 4001 ); 4002 } 4003 4004 rr.requestRead(); 4005 } 4006 4007 void requestRead() { 4008 DWORD ignored; 4009 if(!ReadDirectoryChangesW( 4010 hDirectory, 4011 buffer.ptr, 4012 cast(int) buffer.length, 4013 recursive, 4014 FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, 4015 &ignored, 4016 &overlapped, 4017 &overlappedCompletionRoutine 4018 )) { 4019 auto error = GetLastError(); 4020 /+ 4021 if(error == ERROR_IO_PENDING) { 4022 // not expected here, the docs say it returns true when queued 4023 } 4024 +/ 4025 4026 throw new SystemApiException("ReadDirectoryChangesW", error); 4027 } 4028 } 4029 } else version(Arsd_core_epoll) { 4030 static int inotifyfd = -1; // this is TLS since it is associated with the thread's event loop 4031 static ICoreEventLoop.UnregisterToken inotifyToken; 4032 static CallbackHelper inotifycb; 4033 static DirectoryWatcher[int] watchMappings; 4034 4035 static ~this() { 4036 if(inotifyfd != -1) { 4037 close(inotifyfd); 4038 inotifyfd = -1; 4039 } 4040 } 4041 4042 import core.sys.linux.sys.inotify; 4043 4044 int watchId = -1; 4045 4046 static void inotifyReady() { 4047 // read from it 4048 ubyte[256 /* NAME_MAX + 1 */ + inotify_event.sizeof] sbuffer; 4049 4050 auto ret = read(inotifyfd, sbuffer.ptr, sbuffer.length); 4051 if(ret == -1) { 4052 auto errno = errno; 4053 if(errno == EAGAIN || errno == EWOULDBLOCK) 4054 return; 4055 throw new SystemApiException("read inotify", errno); 4056 } else if(ret == 0) { 4057 assert(0, "I don't think this is ever supposed to happen"); 4058 } 4059 4060 auto buffer = sbuffer[0 .. ret]; 4061 4062 while(buffer.length > 0) { 4063 inotify_event* event = cast(inotify_event*) buffer.ptr; 4064 buffer = buffer[inotify_event.sizeof .. $]; 4065 char[] filename = cast(char[]) buffer[0 .. event.len]; 4066 buffer = buffer[event.len .. $]; 4067 4068 // note that filename is padded with zeroes, so it is actually a stringz 4069 4070 if(auto obj = event.wd in watchMappings) { 4071 (*obj).changeHandler( 4072 FilePath(stringz(filename.ptr).borrow.idup), // FIXME: this is a relative path 4073 ChangeOperation.unknown // FIXME 4074 ); 4075 } else { 4076 // it has probably already been removed 4077 } 4078 } 4079 } 4080 } else version(Arsd_core_kqueue) { 4081 int fd; 4082 CallbackHelper cb; 4083 } 4084 4085 FilePath path; 4086 string globPattern; 4087 bool recursive; 4088 void delegate(FilePath filename, ChangeOperation op) changeHandler; 4089 } 4090 4091 enum ChangeOperation { 4092 unknown, 4093 deleted, // NOTE_DELETE, IN_DELETE, FILE_NOTIFY_CHANGE_FILE_NAME 4094 written, // NOTE_WRITE / NOTE_EXTEND / NOTE_TRUNCATE, IN_MODIFY, FILE_NOTIFY_CHANGE_LAST_WRITE / FILE_NOTIFY_CHANGE_SIZE 4095 renamed, // NOTE_RENAME, the moved from/to in linux, FILE_NOTIFY_CHANGE_FILE_NAME 4096 metadataChanged // NOTE_ATTRIB, IN_ATTRIB, FILE_NOTIFY_CHANGE_ATTRIBUTES 4097 4098 // there is a NOTE_OPEN on freebsd 13, and the access change on Windows. and an open thing on linux. so maybe i can do note open/note_read too. 4099 } 4100 4101 /+ 4102 Windows and Linux work best when you watch directories. The operating system tells you the name of files as they change. 4103 4104 BSD doesn't support this. You can only get names and reports when a file is modified by watching specific files. AS such, when you watch a directory on those systems, your delegate will be called with a null path. Cross-platform applications should check for this and not assume the name is always usable. 4105 4106 inotify is kinda clearly the best of the bunch, with Windows in second place, and kqueue dead last. 4107 4108 4109 If path to watch is a directory, it signals when a file inside the directory (only one layer deep) is created or modified. This is the most efficient on Windows and Linux. 4110 4111 If a path is a file, it only signals when that specific file is written. This is most efficient on BSD. 4112 4113 4114 The delegate is called when something happens. Note that the path modified may not be accurate on all systems when you are watching a directory. 4115 +/ 4116 4117 /++ 4118 Watches a directory and its contents. If the `globPattern` is `null`, it will not attempt to add child items but also will not filter it, meaning you will be left with platform-specific behavior. 4119 4120 On Windows, the globPattern is just used to filter events. 4121 4122 On Linux, the `recursive` flag, if set, will cause it to add additional OS-level watches for each subdirectory. 4123 4124 On BSD, anything other than a null pattern will cause a directory scan to add files to the watch list. 4125 4126 For best results, use the most limited thing you need, as watches can get quite involved on the bsd systems. 4127 4128 Newly added files and subdirectories may not be automatically added in all cases, meaning if it is added and then subsequently modified, you might miss a notification. 4129 4130 If the event queue is too busy, the OS may skip a notification. 4131 4132 You should always offer some way for the user to force a refresh and not rely on notifications being present; they are a convenience when they work, not an always reliable method. 4133 +/ 4134 this(FilePath directoryToWatch, string globPattern, bool recursive, void delegate(FilePath pathModified, ChangeOperation op) dg) { 4135 this.path = directoryToWatch; 4136 this.globPattern = globPattern; 4137 this.recursive = recursive; 4138 this.changeHandler = dg; 4139 4140 version(Arsd_core_windows) { 4141 WCharzBuffer wname = directoryToWatch.path; 4142 buffer = new ubyte[](1024); 4143 hDirectory = CreateFileW( 4144 wname.ptr, 4145 GENERIC_READ, 4146 FILE_SHARE_READ, 4147 null, 4148 OPEN_EXISTING, 4149 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS, 4150 null 4151 ); 4152 if(hDirectory == INVALID_HANDLE_VALUE) 4153 throw new SystemApiException("CreateFileW", GetLastError()); 4154 4155 requestRead(); 4156 } else version(Arsd_core_epoll) { 4157 auto el = getThisThreadEventLoop(); 4158 4159 // no need for sync because it is thread-local 4160 if(inotifyfd == -1) { 4161 inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 4162 if(inotifyfd == -1) 4163 throw new SystemApiException("inotify_init1", errno); 4164 4165 inotifycb = new CallbackHelper(&inotifyReady); 4166 inotifyToken = el.addCallbackOnFdReadable(inotifyfd, inotifycb); 4167 } 4168 4169 uint event_mask = IN_CREATE | IN_MODIFY | IN_DELETE; // FIXME 4170 CharzBuffer dtw = directoryToWatch.path; 4171 auto watchId = inotify_add_watch(inotifyfd, dtw.ptr, event_mask); 4172 if(watchId < -1) 4173 throw new SystemApiException("inotify_add_watch", errno, [SavedArgument("path", LimitedVariant(directoryToWatch.path))]); 4174 4175 watchMappings[watchId] = this; 4176 4177 // FIXME: recursive needs to add child things individually 4178 4179 } else version(Arsd_core_kqueue) { 4180 auto el = cast(CoreEventLoopImplementation) getThisThreadEventLoop(); 4181 4182 // FIXME: need to scan for globPattern 4183 // when a new file is added, i'll have to diff my list to detect it and open it too 4184 // and recursive might need to scan down too. 4185 4186 kevent_t ev; 4187 4188 import core.sys.posix.fcntl; 4189 CharzBuffer buffer = CharzBuffer(directoryToWatch.path); 4190 fd = ErrnoEnforce!open(buffer.ptr, O_RDONLY); 4191 setCloExec(fd); 4192 4193 cb = new CallbackHelper(&triggered); 4194 4195 EV_SET(&ev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_WRITE, 0, cast(void*) cb); 4196 ErrnoEnforce!kevent(el.kqueuefd, &ev, 1, null, 0, null); 4197 } else assert(0, "Not yet implemented for this platform"); 4198 } 4199 4200 private void triggered() { 4201 writeln("triggered"); 4202 } 4203 4204 void dispose() { 4205 version(Arsd_core_windows) { 4206 CloseHandle(hDirectory); 4207 } else version(Arsd_core_epoll) { 4208 watchMappings.remove(watchId); // I could also do this on the IN_IGNORE notification but idk 4209 inotify_rm_watch(inotifyfd, watchId); 4210 } else version(Arsd_core_kqueue) { 4211 ErrnoEnforce!close(fd); 4212 fd = -1; 4213 } 4214 } 4215 } 4216 4217 version(none) 4218 void main() { 4219 4220 // auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation, AsyncFile.RequirePreexisting.yes); 4221 4222 /+ 4223 getFiles("c:/windows\\", (string filename, bool isDirectory) { 4224 writeln(filename, " ", isDirectory ? "[dir]": "[file]"); 4225 }); 4226 +/ 4227 4228 auto w = new DirectoryWatcher(FilePath("."), "*", false, (path, op) { 4229 writeln(path.path); 4230 }); 4231 getThisThreadEventLoop().run(() => false); 4232 } 4233 4234 /++ 4235 This starts up a local pipe. If it is already claimed, it just communicates with the existing one through the interface. 4236 +/ 4237 class SingleInstanceApplication { 4238 // FIXME 4239 } 4240 4241 version(none) 4242 void main() { 4243 4244 auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation, AsyncFile.RequirePreexisting.yes); 4245 4246 auto buffer = cast(ubyte[]) "hello"; 4247 auto wr = new AsyncWriteRequest(file, buffer, 0); 4248 wr.start(); 4249 4250 wr.waitForCompletion(); 4251 4252 file.close(); 4253 } 4254 4255 /++ 4256 Implementation details of some requests. You shouldn't need to know any of this, the interface is all public. 4257 +/ 4258 mixin template OverlappedIoRequest(Response, LowLevelOperation) { 4259 private { 4260 LowLevelOperation llo; 4261 4262 OwnedClass!Response response; 4263 4264 version(Windows) { 4265 OVERLAPPED overlapped; 4266 4267 extern(Windows) 4268 static void overlappedCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped) { 4269 typeof(this) rr = cast(typeof(this)) (cast(void*) lpOverlapped - typeof(this).overlapped.offsetof); 4270 4271 rr.response = typeof(rr.response)(SystemErrorCode(dwErrorCode), rr.llo.buffer[0 .. dwNumberOfBytesTransferred]); 4272 rr.state_ = State.complete; 4273 4274 // FIXME: on complete? 4275 4276 // this will queue our CallbackHelper and that should be run at the end of the event loop after it is woken up by the APC run 4277 } 4278 } 4279 4280 version(Posix) { 4281 ICoreEventLoop.RearmToken eventRegistration; 4282 CallbackHelper cb; 4283 4284 final CallbackHelper getCb() { 4285 if(cb is null) 4286 cb = new CallbackHelper(&cbImpl); 4287 return cb; 4288 } 4289 4290 final void cbImpl() { 4291 // it is ready to complete, time to do it 4292 auto ret = llo(); 4293 markCompleted(ret, errno); 4294 } 4295 4296 void markCompleted(long ret, int errno) { 4297 // maybe i should queue an apc to actually do it, to ensure the event loop has cycled... FIXME 4298 if(ret == -1) 4299 response = typeof(response)(SystemErrorCode(errno), null); 4300 else 4301 response = typeof(response)(SystemErrorCode(0), llo.buffer[0 .. cast(size_t) ret]); 4302 state_ = State.complete; 4303 } 4304 } 4305 } 4306 4307 enum State { 4308 unused, 4309 started, 4310 inProgress, 4311 complete 4312 } 4313 private State state_; 4314 4315 override void start() { 4316 assert(state_ == State.unused); 4317 4318 state_ = State.started; 4319 4320 version(Windows) { 4321 if(llo(&overlapped, &overlappedCompletionRoutine)) { 4322 // all good, though GetLastError() might have some informative info 4323 } else { 4324 // operation failed, the operation is always ReadFileEx or WriteFileEx so it won't give the io pending thing here 4325 // should i issue error async? idk 4326 state_ = State.complete; 4327 throw new SystemApiException(llo.errorString(), GetLastError()); 4328 } 4329 4330 // ReadFileEx always queues, even if it completed synchronously. I *could* check the get overlapped result and sleepex here but i'm prolly better off just letting the event loop do its thing anyway. 4331 } else version(Posix) { 4332 4333 // first try to just do it 4334 auto ret = llo(); 4335 4336 auto errno = errno; 4337 if(ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { // unable to complete right now, register and try when it is ready 4338 eventRegistration = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(this.llo.file.handle, this.getCb); 4339 } else { 4340 // i could set errors sync or async and since it couldn't even start, i think a sync exception is the right way 4341 if(ret == -1) 4342 throw new SystemApiException(llo.errorString(), errno); 4343 markCompleted(ret, errno); // it completed synchronously (if it is an error nor not is handled by the completion handler) 4344 } 4345 } 4346 } 4347 4348 4349 override void cancel() { 4350 if(state_ == State.complete) 4351 return; // it has already finished, just leave it alone, no point discarding what is already done 4352 version(Windows) { 4353 if(state_ != State.unused) 4354 Win32Enforce!CancelIoEx(llo.file.AbstractFile.handle, &overlapped); 4355 // Windows will notify us when the cancellation is complete, so we need to wait for that before updating the state 4356 } else version(Posix) { 4357 if(state_ != State.unused) 4358 eventRegistration.unregister(); 4359 markCompleted(-1, ECANCELED); 4360 } 4361 } 4362 4363 override bool isComplete() { 4364 // just always let the event loop do it instead 4365 return state_ == State.complete; 4366 4367 /+ 4368 version(Windows) { 4369 return HasOverlappedIoCompleted(&overlapped); 4370 } else version(Posix) { 4371 return state_ == State.complete; 4372 4373 } 4374 +/ 4375 } 4376 4377 override Response waitForCompletion() { 4378 if(state_ == State.unused) 4379 start(); 4380 4381 // FIXME: if we are inside a fiber, we can set a oncomplete callback and then yield instead... 4382 if(state_ != State.complete) 4383 getThisThreadEventLoop().run(&isComplete); 4384 4385 /+ 4386 version(Windows) { 4387 SleepEx(INFINITE, true); 4388 4389 //DWORD numberTransferred; 4390 //Win32Enforce!GetOverlappedResult(file.handle, &overlapped, &numberTransferred, true); 4391 } else version(Posix) { 4392 getThisThreadEventLoop().run(&isComplete); 4393 } 4394 +/ 4395 4396 return response; 4397 } 4398 } 4399 4400 /++ 4401 You can write to a file asynchronously by creating one of these. 4402 +/ 4403 version(HasSocket) final class AsyncWriteRequest : AsyncOperationRequest { 4404 struct LowLevelOperation { 4405 AsyncFile file; 4406 ubyte[] buffer; 4407 long offset; 4408 4409 this(typeof(this.tupleof) args) { 4410 this.tupleof = args; 4411 } 4412 4413 version(Windows) { 4414 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 4415 overlapped.Offset = (cast(ulong) offset) & 0xffff_ffff; 4416 overlapped.OffsetHigh = ((cast(ulong) offset) >> 32) & 0xffff_ffff; 4417 return WriteFileEx(file.handle, buffer.ptr, cast(int) buffer.length, overlapped, ocr); 4418 } 4419 } else { 4420 auto opCall() { 4421 return core.sys.posix.unistd.write(file.handle, buffer.ptr, buffer.length); 4422 } 4423 } 4424 4425 string errorString() { 4426 return "Write"; 4427 } 4428 } 4429 mixin OverlappedIoRequest!(AsyncWriteResponse, LowLevelOperation); 4430 4431 this(AsyncFile file, ubyte[] buffer, long offset) { 4432 this.llo = LowLevelOperation(file, buffer, offset); 4433 response = typeof(response).defaultConstructed; 4434 } 4435 } 4436 4437 /++ 4438 4439 +/ 4440 class AsyncWriteResponse : AsyncOperationResponse { 4441 const ubyte[] bufferWritten; 4442 const SystemErrorCode errorCode; 4443 4444 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 4445 this.errorCode = errorCode; 4446 this.bufferWritten = bufferWritten; 4447 } 4448 4449 override bool wasSuccessful() { 4450 return errorCode.wasSuccessful; 4451 } 4452 } 4453 4454 /++ 4455 4456 +/ 4457 version(HasSocket) final class AsyncReadRequest : AsyncOperationRequest { 4458 struct LowLevelOperation { 4459 AsyncFile file; 4460 ubyte[] buffer; 4461 long offset; 4462 4463 this(typeof(this.tupleof) args) { 4464 this.tupleof = args; 4465 } 4466 4467 version(Windows) { 4468 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 4469 overlapped.Offset = (cast(ulong) offset) & 0xffff_ffff; 4470 overlapped.OffsetHigh = ((cast(ulong) offset) >> 32) & 0xffff_ffff; 4471 return ReadFileEx(file.handle, buffer.ptr, cast(int) buffer.length, overlapped, ocr); 4472 } 4473 } else { 4474 auto opCall() { 4475 return core.sys.posix.unistd.read(file.handle, buffer.ptr, buffer.length); 4476 } 4477 } 4478 4479 string errorString() { 4480 return "Read"; 4481 } 4482 } 4483 mixin OverlappedIoRequest!(AsyncReadResponse, LowLevelOperation); 4484 4485 /++ 4486 The file must have the overlapped flag enabled on Windows and the nonblock flag set on Posix. 4487 4488 The buffer MUST NOT be touched by you - not used by another request, modified, read, or freed, including letting a static array going out of scope - until this request's `isComplete` returns `true`. 4489 4490 The offset is where to start reading a disk file. For all other types of files, pass 0. 4491 +/ 4492 this(AsyncFile file, ubyte[] buffer, long offset) { 4493 this.llo = LowLevelOperation(file, buffer, offset); 4494 response = typeof(response).defaultConstructed; 4495 } 4496 4497 /++ 4498 4499 +/ 4500 // abstract void repeat(); 4501 } 4502 4503 /++ 4504 4505 +/ 4506 class AsyncReadResponse : AsyncOperationResponse { 4507 const ubyte[] bufferRead; 4508 const SystemErrorCode errorCode; 4509 4510 this(SystemErrorCode errorCode, const(ubyte)[] bufferRead) { 4511 this.errorCode = errorCode; 4512 this.bufferRead = bufferRead; 4513 } 4514 4515 override bool wasSuccessful() { 4516 return errorCode.wasSuccessful; 4517 } 4518 } 4519 4520 /+ 4521 Tasks: 4522 startTask() 4523 startSubTask() - what if it just did this when it knows it is being run from inside a task? 4524 runHelperFunction() - whomever it reports to is the parent 4525 +/ 4526 4527 version(HasThread) class ScheduableTask : Fiber { 4528 private void delegate() dg; 4529 4530 // linked list stuff 4531 private static ScheduableTask taskRoot; 4532 private ScheduableTask previous; 4533 private ScheduableTask next; 4534 4535 // need the controlling thread to know how to wake it up if it receives a message 4536 private Thread controllingThread; 4537 4538 // the api 4539 4540 this(void delegate() dg) { 4541 assert(dg !is null); 4542 4543 this.dg = dg; 4544 super(&taskRunner); 4545 4546 if(taskRoot !is null) { 4547 this.next = taskRoot; 4548 taskRoot.previous = this; 4549 } 4550 taskRoot = this; 4551 } 4552 4553 /+ 4554 enum BehaviorOnCtrlC { 4555 ignore, 4556 cancel, 4557 deliverMessage 4558 } 4559 +/ 4560 4561 private bool cancelled; 4562 4563 public void cancel() { 4564 this.cancelled = true; 4565 // if this is running, we can throw immediately 4566 // otherwise if we're calling from an appropriate thread, we can call it immediately 4567 // otherwise we need to queue a wakeup to its own thread. 4568 // tbh we should prolly just queue it every time 4569 } 4570 4571 private void taskRunner() { 4572 try { 4573 dg(); 4574 } catch(TaskCancelledException tce) { 4575 // this space intentionally left blank; 4576 // the purpose of this exception is to just 4577 // let the fiber's destructors run before we 4578 // let it die. 4579 } catch(Throwable t) { 4580 if(taskUncaughtException is null) { 4581 throw t; 4582 } else { 4583 taskUncaughtException(t); 4584 } 4585 } finally { 4586 if(this is taskRoot) { 4587 taskRoot = taskRoot.next; 4588 if(taskRoot !is null) 4589 taskRoot.previous = null; 4590 } else { 4591 assert(this.previous !is null); 4592 assert(this.previous.next is this); 4593 this.previous.next = this.next; 4594 if(this.next !is null) 4595 this.next.previous = this.previous; 4596 } 4597 } 4598 } 4599 } 4600 4601 /++ 4602 4603 +/ 4604 void delegate(Throwable t) taskUncaughtException; 4605 4606 /++ 4607 Gets an object that lets you control a schedulable task (which is a specialization of a fiber) and can be used in an `if` statement. 4608 4609 --- 4610 if(auto controller = inSchedulableTask()) { 4611 controller.yieldUntilReadable(...); 4612 } 4613 --- 4614 4615 History: 4616 Added August 11, 2023 (dub v11.1) 4617 +/ 4618 version(HasThread) SchedulableTaskController inSchedulableTask() { 4619 import core.thread.fiber; 4620 4621 if(auto fiber = Fiber.getThis) { 4622 return SchedulableTaskController(cast(ScheduableTask) fiber); 4623 } 4624 4625 return SchedulableTaskController(null); 4626 } 4627 4628 /// ditto 4629 version(HasThread) struct SchedulableTaskController { 4630 private this(ScheduableTask fiber) { 4631 this.fiber = fiber; 4632 } 4633 4634 private ScheduableTask fiber; 4635 4636 /++ 4637 4638 +/ 4639 bool opCast(T : bool)() { 4640 return fiber !is null; 4641 } 4642 4643 /++ 4644 4645 +/ 4646 version(Posix) 4647 void yieldUntilReadable(NativeFileHandle handle) { 4648 assert(fiber !is null); 4649 4650 auto cb = new CallbackHelper(() { fiber.call(); }); 4651 4652 // FIXME: if the fd is already registered in this thread it can throw... 4653 version(Windows) 4654 auto rearmToken = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(handle, cb); 4655 else 4656 auto rearmToken = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(handle, cb); 4657 4658 // FIXME: this is only valid if the fiber is only ever going to run in this thread! 4659 fiber.yield(); 4660 4661 rearmToken.unregister(); 4662 4663 // what if there are other messages, like a ctrl+c? 4664 if(fiber.cancelled) 4665 throw new TaskCancelledException(); 4666 } 4667 4668 version(Windows) 4669 void yieldUntilSignaled(NativeFileHandle handle) { 4670 // add it to the WaitForMultipleObjects thing w/ a cb 4671 } 4672 } 4673 4674 class TaskCancelledException : object.Exception { 4675 this() { 4676 super("Task cancelled"); 4677 } 4678 } 4679 4680 version(HasThread) private class CoreWorkerThread : Thread { 4681 this(EventLoopType type) { 4682 this.type = type; 4683 4684 // task runners are supposed to have smallish stacks since they either just run a single callback or call into fibers 4685 // the helper runners might be a bit bigger tho 4686 super(&run); 4687 } 4688 void run() { 4689 eventLoop = getThisThreadEventLoop(this.type); 4690 atomicOp!"+="(startedCount, 1); 4691 atomicOp!"+="(runningCount, 1); 4692 scope(exit) { 4693 atomicOp!"-="(runningCount, 1); 4694 } 4695 4696 eventLoop.run(() => cancelled); 4697 } 4698 4699 private bool cancelled; 4700 4701 void cancel() { 4702 cancelled = true; 4703 } 4704 4705 EventLoopType type; 4706 ICoreEventLoop eventLoop; 4707 4708 __gshared static { 4709 CoreWorkerThread[] taskRunners; 4710 CoreWorkerThread[] helperRunners; 4711 ICoreEventLoop mainThreadLoop; 4712 4713 // for the helper function thing on the bsds i could have my own little circular buffer of availability 4714 4715 shared(int) startedCount; 4716 shared(int) runningCount; 4717 4718 bool started; 4719 4720 void setup(int numberOfTaskRunners, int numberOfHelpers) { 4721 assert(!started); 4722 synchronized { 4723 mainThreadLoop = getThisThreadEventLoop(); 4724 4725 foreach(i; 0 .. numberOfTaskRunners) { 4726 auto nt = new CoreWorkerThread(EventLoopType.TaskRunner); 4727 taskRunners ~= nt; 4728 nt.start(); 4729 } 4730 foreach(i; 0 .. numberOfHelpers) { 4731 auto nt = new CoreWorkerThread(EventLoopType.HelperWorker); 4732 helperRunners ~= nt; 4733 nt.start(); 4734 } 4735 4736 const expectedCount = numberOfHelpers + numberOfTaskRunners; 4737 4738 while(startedCount < expectedCount) { 4739 Thread.yield(); 4740 } 4741 4742 started = true; 4743 } 4744 } 4745 4746 void cancelAll() { 4747 foreach(runner; taskRunners) 4748 runner.cancel(); 4749 foreach(runner; helperRunners) 4750 runner.cancel(); 4751 4752 } 4753 } 4754 } 4755 4756 private int numberOfCpus() { 4757 return 4; // FIXME 4758 } 4759 4760 /++ 4761 To opt in to the full functionality of this module with customization opportunity, create one and only one of these objects that is valid for exactly the lifetime of the application. 4762 4763 Normally, this means writing a main like this: 4764 4765 --- 4766 import arsd.core; 4767 void main() { 4768 ArsdCoreApplication app = ArsdCoreApplication("Your app name"); 4769 4770 // do your setup here 4771 4772 // the rest of your code here 4773 } 4774 --- 4775 4776 Its destructor runs the event loop then waits to for the workers to finish to clean them up. 4777 +/ 4778 // FIXME: single instance? 4779 version(HasThread) struct ArsdCoreApplication { 4780 private ICoreEventLoop impl; 4781 4782 /++ 4783 default number of threads is to split your cpus between blocking function runners and task runners 4784 +/ 4785 this(string applicationName) { 4786 auto num = numberOfCpus(); 4787 num /= 2; 4788 if(num <= 0) 4789 num = 1; 4790 this(applicationName, num, num); 4791 } 4792 4793 /++ 4794 4795 +/ 4796 this(string applicationName, int numberOfTaskRunners, int numberOfHelpers) { 4797 impl = getThisThreadEventLoop(EventLoopType.Explicit); 4798 CoreWorkerThread.setup(numberOfTaskRunners, numberOfHelpers); 4799 } 4800 4801 @disable this(); 4802 @disable this(this); 4803 /++ 4804 This must be deterministically destroyed. 4805 +/ 4806 @disable new(); 4807 4808 ~this() { 4809 if(!alreadyRun) 4810 run(); 4811 exitApplication(); 4812 waitForWorkersToExit(3000); 4813 } 4814 4815 void exitApplication() { 4816 CoreWorkerThread.cancelAll(); 4817 } 4818 4819 void waitForWorkersToExit(int timeoutMilliseconds) { 4820 4821 } 4822 4823 private bool alreadyRun; 4824 4825 void run() { 4826 impl.run(() => false); 4827 alreadyRun = true; 4828 } 4829 } 4830 4831 4832 private class CoreEventLoopImplementation : ICoreEventLoop { 4833 version(EmptyEventLoop) RunOnceResult runOnce(Duration timeout = Duration.max) { return RunOnceResult(RunOnceResult.Possibilities.LocalExit); } 4834 version(EmptyCoreEvent) 4835 { 4836 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb){return typeof(return).init;} 4837 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb){return typeof(return).init;} 4838 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb){return typeof(return).init;} 4839 private void rearmFd(RearmToken token) {} 4840 } 4841 4842 4843 private { 4844 static struct LoopIterationDelegate { 4845 void delegate() dg; 4846 uint flags; 4847 } 4848 LoopIterationDelegate[] loopIterationDelegates; 4849 4850 void runLoopIterationDelegates() { 4851 foreach(lid; loopIterationDelegates) 4852 lid.dg(); 4853 } 4854 } 4855 4856 void addDelegateOnLoopIteration(void delegate() dg, uint timingFlags) { 4857 loopIterationDelegates ~= LoopIterationDelegate(dg, timingFlags); 4858 } 4859 4860 version(Arsd_core_kqueue) { 4861 // this thread apc dispatches go as a custom event to the queue 4862 // the other queues go through one byte at a time pipes (barf). freebsd 13 and newest nbsd have eventfd too tho so maybe i can use them but the other kqueue systems don't. 4863 4864 RunOnceResult runOnce(Duration timeout = Duration.max) { 4865 kevent_t[16] ev; 4866 //timespec tout = timespec(1, 0); 4867 auto nev = kevent(kqueuefd, null, 0, ev.ptr, ev.length, null/*&tout*/); 4868 if(nev == -1) { 4869 // FIXME: EINTR 4870 throw new SystemApiException("kevent", errno); 4871 } else if(nev == 0) { 4872 // timeout 4873 } else { 4874 foreach(event; ev[0 .. nev]) { 4875 if(event.filter == EVFILT_SIGNAL) { 4876 // FIXME: I could prolly do this better tbh 4877 markSignalOccurred(cast(int) event.ident); 4878 signalChecker(); 4879 } else { 4880 // FIXME: event.filter more specific? 4881 CallbackHelper cb = cast(CallbackHelper) event.udata; 4882 cb.call(); 4883 } 4884 } 4885 } 4886 4887 runLoopIterationDelegates(); 4888 4889 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 4890 } 4891 4892 // FIXME: idk how to make one event that multiple kqueues can listen to w/o being shared 4893 // maybe a shared kqueue could work that the thread kqueue listen to (which i rejected for 4894 // epoll cuz it caused thundering herd problems but maybe it'd work here) 4895 4896 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb) { 4897 kevent_t ev; 4898 4899 EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 4900 4901 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4902 4903 return UnregisterToken(this, fd, cb); 4904 } 4905 4906 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb) { 4907 kevent_t ev; 4908 4909 EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 4910 4911 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4912 4913 return RearmToken(true, this, fd, cb, 0); 4914 } 4915 4916 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb) { 4917 kevent_t ev; 4918 4919 EV_SET(&ev, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 4920 4921 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4922 4923 return RearmToken(false, this, fd, cb, 0); 4924 } 4925 4926 private void rearmFd(RearmToken token) { 4927 if(token.readable) 4928 cast(void) addCallbackOnFdReadableOneShot(token.fd, token.cb); 4929 else 4930 cast(void) addCallbackOnFdWritableOneShot(token.fd, token.cb); 4931 } 4932 4933 private void triggerGlobalEvent() { 4934 ubyte a; 4935 import core.sys.posix.unistd; 4936 write(kqueueGlobalFd[1], &a, 1); 4937 } 4938 4939 private this() { 4940 kqueuefd = ErrnoEnforce!kqueue(); 4941 setCloExec(kqueuefd); // FIXME O_CLOEXEC 4942 4943 if(kqueueGlobalFd[0] == 0) { 4944 import core.sys.posix.unistd; 4945 pipe(kqueueGlobalFd); 4946 setCloExec(kqueueGlobalFd[0]); 4947 setCloExec(kqueueGlobalFd[1]); 4948 4949 signal(SIGINT, SIG_IGN); // FIXME 4950 } 4951 4952 kevent_t ev; 4953 4954 EV_SET(&ev, SIGCHLD, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, null); 4955 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4956 EV_SET(&ev, SIGINT, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, null); 4957 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4958 4959 globalEventSent = new CallbackHelper(&readGlobalEvent); 4960 EV_SET(&ev, kqueueGlobalFd[0], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, cast(void*) globalEventSent); 4961 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4962 } 4963 4964 private int kqueuefd = -1; 4965 4966 private CallbackHelper globalEventSent; 4967 void readGlobalEvent() { 4968 kevent_t event; 4969 4970 import core.sys.posix.unistd; 4971 ubyte a; 4972 read(kqueueGlobalFd[0], &a, 1); 4973 4974 // FIXME: the thread is woken up, now we need to check the circualr buffer queue 4975 } 4976 4977 private __gshared int[2] kqueueGlobalFd; 4978 } 4979 4980 /+ 4981 // this setup needs no extra allocation 4982 auto op = read(file, buffer); 4983 op.oncomplete = &thisfiber.call; 4984 op.start(); 4985 thisfiber.yield(); 4986 auto result = op.waitForCompletion(); // guaranteed to return instantly thanks to previous setup 4987 4988 can generically abstract that into: 4989 4990 auto result = thisTask.await(read(file, buffer)); 4991 4992 4993 You MUST NOT use buffer in any way - not read, modify, deallocate, reuse, anything - until the PendingOperation is complete. 4994 4995 Note that PendingOperation may just be a wrapper around an internally allocated object reference... but then if you do a waitForFirstToComplete what happens? 4996 4997 those could of course just take the value type things 4998 +/ 4999 5000 5001 version(Arsd_core_windows) { 5002 // all event loops share the one iocp, Windows 5003 // manages how to do it 5004 __gshared HANDLE iocpTaskRunners; 5005 __gshared HANDLE iocpWorkers; 5006 5007 HANDLE[] handles; 5008 CallbackHelper[] handlesCbs; 5009 5010 void unregisterHandle(HANDLE handle, CallbackHelper cb) { 5011 foreach(idx, h; handles) 5012 if(h is handle && handlesCbs[idx] is cb) { 5013 handles[idx] = handles[$-1]; 5014 handles = handles[0 .. $-1].assumeSafeAppend; 5015 5016 handlesCbs[idx] = handlesCbs[$-1]; 5017 handlesCbs = handlesCbs[0 .. $-1].assumeSafeAppend; 5018 } 5019 } 5020 5021 UnregisterToken addCallbackOnHandleReady(HANDLE handle, CallbackHelper cb) { 5022 handles ~= handle; 5023 handlesCbs ~= cb; 5024 5025 return UnregisterToken(this, handle, cb); 5026 } 5027 5028 // i think to terminate i just have to post the message at least once for every thread i know about, maybe a few more times for threads i don't know about. 5029 5030 bool isWorker; // if it is a worker we wait on the iocp, if not we wait on msg 5031 5032 RunOnceResult runOnce(Duration timeout = Duration.max) { 5033 if(isWorker) { 5034 // this function is only supported on Windows Vista and up, so using this 5035 // means dropping support for XP. 5036 //GetQueuedCompletionStatusEx(); 5037 assert(0); // FIXME 5038 } else { 5039 auto wto = 0; 5040 5041 auto waitResult = MsgWaitForMultipleObjectsEx( 5042 cast(int) handles.length, handles.ptr, 5043 (wto == 0 ? INFINITE : wto), /* timeout */ 5044 0x04FF, /* QS_ALLINPUT */ 5045 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 5046 5047 enum WAIT_OBJECT_0 = 0; 5048 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 5049 auto h = handles[waitResult - WAIT_OBJECT_0]; 5050 auto cb = handlesCbs[waitResult - WAIT_OBJECT_0]; 5051 cb.call(); 5052 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 5053 // message ready 5054 int count; 5055 MSG message; 5056 while(PeekMessage(&message, null, 0, 0, PM_NOREMOVE)) { // need to peek since sometimes MsgWaitForMultipleObjectsEx returns even though GetMessage can block. tbh i don't fully understand it but the docs say it is foreground activation 5057 auto ret = GetMessage(&message, null, 0, 0); 5058 if(ret == -1) 5059 throw new WindowsApiException("GetMessage", GetLastError()); 5060 TranslateMessage(&message); 5061 DispatchMessage(&message); 5062 5063 count++; 5064 if(count > 10) 5065 break; // take the opportunity to catch up on other events 5066 5067 if(ret == 0) { // WM_QUIT 5068 exitApplication(); 5069 } 5070 } 5071 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 5072 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 5073 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 5074 // timeout, should never happen since we aren't using it 5075 } else if(waitResult == 0xFFFFFFFF) { 5076 // failed 5077 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 5078 } else { 5079 // idk.... 5080 } 5081 } 5082 5083 runLoopIterationDelegates(); 5084 5085 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 5086 } 5087 } 5088 5089 version(Posix) { 5090 private __gshared uint sigChildHappened = 0; 5091 private __gshared uint sigIntrHappened = 0; 5092 5093 static void signalChecker() { 5094 if(cas(&sigChildHappened, 1, 0)) { 5095 while(true) { // multiple children could have exited before we processed the notification 5096 5097 import core.sys.posix.sys.wait; 5098 5099 int status; 5100 auto pid = waitpid(-1, &status, WNOHANG); 5101 if(pid == -1) { 5102 import core.stdc.errno; 5103 auto errno = errno; 5104 if(errno == ECHILD) 5105 break; // also all done, there are no children left 5106 // no need to check EINTR since we set WNOHANG 5107 throw new ErrnoApiException("waitpid", errno); 5108 } 5109 if(pid == 0) 5110 break; // all done, all children are still running 5111 5112 // look up the pid for one of our objects 5113 // if it is found, inform it of its status 5114 // and then inform its controlling thread 5115 // to wake up so it can check its waitForCompletion, 5116 // trigger its callbacks, etc. 5117 5118 ExternalProcess.recordChildTerminated(pid, status); 5119 } 5120 5121 } 5122 if(cas(&sigIntrHappened, 1, 0)) { 5123 // FIXME 5124 import core.stdc.stdlib; 5125 exit(0); 5126 } 5127 } 5128 5129 /++ 5130 Informs the arsd.core system that the given signal happened. You can call this from inside a signal handler. 5131 +/ 5132 public static void markSignalOccurred(int sigNumber) nothrow { 5133 import core.sys.posix.unistd; 5134 5135 if(sigNumber == SIGCHLD) 5136 volatileStore(&sigChildHappened, 1); 5137 if(sigNumber == SIGINT) 5138 volatileStore(&sigIntrHappened, 1); 5139 5140 version(Arsd_core_epoll) { 5141 ulong writeValue = 1; 5142 write(signalPipeFd, &writeValue, writeValue.sizeof); 5143 } 5144 } 5145 } 5146 5147 version(Arsd_core_epoll) { 5148 5149 import core.sys.linux.epoll; 5150 import core.sys.linux.sys.eventfd; 5151 5152 private this() { 5153 5154 if(!globalsInitialized) { 5155 synchronized { 5156 if(!globalsInitialized) { 5157 // blocking signals is problematic because it is inherited by child processes 5158 // and that can be problematic for general purpose stuff so i use a self pipe 5159 // here. though since it is linux, im using an eventfd instead just to notify 5160 signalPipeFd = ErrnoEnforce!eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); 5161 signalReaderCallback = new CallbackHelper(&signalReader); 5162 5163 runInTaskRunnerQueue = new CallbackQueue("task runners", true); 5164 runInHelperThreadQueue = new CallbackQueue("helper threads", true); 5165 5166 setSignalHandlers(); 5167 5168 globalsInitialized = true; 5169 } 5170 } 5171 } 5172 5173 epollfd = epoll_create1(EPOLL_CLOEXEC); 5174 5175 // FIXME: ensure UI events get top priority 5176 5177 // global listeners 5178 5179 // FIXME: i should prolly keep the tokens and release them when tearing down. 5180 5181 cast(void) addCallbackOnFdReadable(signalPipeFd, signalReaderCallback); 5182 if(true) { // FIXME: if this is a task runner vs helper thread vs ui thread 5183 cast(void) addCallbackOnFdReadable(runInTaskRunnerQueue.fd, runInTaskRunnerQueue.callback); 5184 runInTaskRunnerQueue.callback.addref(); 5185 } else { 5186 cast(void) addCallbackOnFdReadable(runInHelperThreadQueue.fd, runInHelperThreadQueue.callback); 5187 runInHelperThreadQueue.callback.addref(); 5188 } 5189 5190 // local listener 5191 thisThreadQueue = new CallbackQueue("this thread", false); 5192 cast(void) addCallbackOnFdReadable(thisThreadQueue.fd, thisThreadQueue.callback); 5193 5194 // what are we going to do about timers? 5195 } 5196 5197 void teardown() { 5198 import core.sys.posix.fcntl; 5199 import core.sys.posix.unistd; 5200 5201 close(epollfd); 5202 epollfd = -1; 5203 5204 thisThreadQueue.teardown(); 5205 5206 // FIXME: should prolly free anything left in the callback queue, tho those could also be GC managed tbh. 5207 } 5208 5209 /+ // i call it explicitly at the thread exit instead, but worker threads aren't really supposed to exit generally speaking till process done anyway 5210 static ~this() { 5211 teardown(); 5212 } 5213 +/ 5214 5215 static void teardownGlobals() { 5216 import core.sys.posix.fcntl; 5217 import core.sys.posix.unistd; 5218 5219 synchronized { 5220 restoreSignalHandlers(); 5221 close(signalPipeFd); 5222 signalReaderCallback.release(); 5223 5224 runInTaskRunnerQueue.teardown(); 5225 runInHelperThreadQueue.teardown(); 5226 5227 globalsInitialized = false; 5228 } 5229 5230 } 5231 5232 5233 private static final class CallbackQueue { 5234 int fd = -1; 5235 string name; 5236 CallbackHelper callback; 5237 SynchronizedCircularBuffer!CallbackHelper queue; 5238 5239 this(string name, bool dequeueIsShared) { 5240 this.name = name; 5241 queue = typeof(queue)(this); 5242 5243 fd = ErrnoEnforce!eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | (dequeueIsShared ? EFD_SEMAPHORE : 0)); 5244 5245 callback = new CallbackHelper(dequeueIsShared ? &sharedDequeueCb : &threadLocalDequeueCb); 5246 } 5247 5248 bool resetEvent() { 5249 import core.sys.posix.unistd; 5250 ulong count; 5251 return read(fd, &count, count.sizeof) == count.sizeof; 5252 } 5253 5254 void sharedDequeueCb() { 5255 if(resetEvent()) { 5256 auto cb = queue.dequeue(); 5257 cb.call(); 5258 cb.release(); 5259 } 5260 } 5261 5262 void threadLocalDequeueCb() { 5263 CallbackHelper[16] buffer; 5264 foreach(cb; queue.dequeueSeveral(buffer[], () { resetEvent(); })) { 5265 cb.call(); 5266 cb.release(); 5267 } 5268 } 5269 5270 void enqueue(CallbackHelper cb) { 5271 if(queue.enqueue(cb)) { 5272 import core.sys.posix.unistd; 5273 ulong count = 1; 5274 ErrnoEnforce!write(fd, &count, count.sizeof); 5275 } else { 5276 throw new ArsdException!"queue is full"(name); 5277 } 5278 } 5279 5280 void teardown() { 5281 import core.sys.posix.fcntl; 5282 import core.sys.posix.unistd; 5283 5284 close(fd); 5285 fd = -1; 5286 5287 callback.release(); 5288 } 5289 } 5290 5291 // there's a global instance of this we refer back to 5292 private __gshared { 5293 bool globalsInitialized; 5294 5295 CallbackHelper signalReaderCallback; 5296 5297 CallbackQueue runInTaskRunnerQueue; 5298 CallbackQueue runInHelperThreadQueue; 5299 5300 int exitEventFd = -1; // FIXME: implement 5301 } 5302 5303 // and then the local loop 5304 private { 5305 int epollfd = -1; 5306 5307 CallbackQueue thisThreadQueue; 5308 } 5309 5310 // signal stuff { 5311 import core.sys.posix.signal; 5312 5313 private __gshared sigaction_t oldSigIntr; 5314 private __gshared sigaction_t oldSigChld; 5315 private __gshared sigaction_t oldSigPipe; 5316 5317 private __gshared int signalPipeFd = -1; 5318 // sigpipe not important, i handle errors on the writes 5319 5320 public static void setSignalHandlers() { 5321 static extern(C) void interruptHandler(int sigNumber) nothrow { 5322 markSignalOccurred(sigNumber); 5323 5324 /+ 5325 // calling the old handler is non-trivial since there can be ignore 5326 // or default or a plain handler or a sigaction 3 arg handler and i 5327 // i don't think it is worth teh complication 5328 sigaction_t* oldHandler; 5329 if(sigNumber == SIGCHLD) 5330 oldHandler = &oldSigChld; 5331 else if(sigNumber == SIGINT) 5332 oldHandler = &oldSigIntr; 5333 if(oldHandler && oldHandler.sa_handler) 5334 oldHandler 5335 +/ 5336 } 5337 5338 sigaction_t n; 5339 n.sa_handler = &interruptHandler; 5340 n.sa_mask = cast(sigset_t) 0; 5341 n.sa_flags = 0; 5342 sigaction(SIGINT, &n, &oldSigIntr); 5343 sigaction(SIGCHLD, &n, &oldSigChld); 5344 5345 n.sa_handler = SIG_IGN; 5346 sigaction(SIGPIPE, &n, &oldSigPipe); 5347 } 5348 5349 public static void restoreSignalHandlers() { 5350 sigaction(SIGINT, &oldSigIntr, null); 5351 sigaction(SIGCHLD, &oldSigChld, null); 5352 sigaction(SIGPIPE, &oldSigPipe, null); 5353 } 5354 5355 private static void signalReader() { 5356 import core.sys.posix.unistd; 5357 ulong number; 5358 read(signalPipeFd, &number, number.sizeof); 5359 5360 signalChecker(); 5361 } 5362 // signal stuff done } 5363 5364 // the any thread poll is just registered in the this thread poll w/ exclusive. nobody actaully epoll_waits 5365 // on the global one directly. 5366 5367 RunOnceResult runOnce(Duration timeout = Duration.max) { 5368 epoll_event[16] events; 5369 auto ret = epoll_wait(epollfd, events.ptr, cast(int) events.length, -1); // FIXME: timeout 5370 if(ret == -1) { 5371 import core.stdc.errno; 5372 if(errno == EINTR) { 5373 return RunOnceResult(RunOnceResult.Possibilities.Interrupted); 5374 } 5375 throw new ErrnoApiException("epoll_wait", errno); 5376 } else if(ret == 0) { 5377 // timeout 5378 } else { 5379 // loop events and call associated callbacks 5380 foreach(event; events[0 .. ret]) { 5381 auto flags = event.events; 5382 auto cbObject = cast(CallbackHelper) event.data.ptr; 5383 5384 // FIXME: or if it is an error... 5385 // EPOLLERR - write end of pipe when read end closed or other error. and EPOLLHUP - terminal hangup or read end when write end close (but it will give 0 reading after that soon anyway) 5386 5387 cbObject.call(); 5388 } 5389 } 5390 5391 runLoopIterationDelegates(); 5392 5393 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 5394 } 5395 5396 // building blocks for low-level integration with the loop 5397 5398 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb) { 5399 epoll_event event; 5400 event.data.ptr = cast(void*) cb; 5401 event.events = EPOLLIN | EPOLLEXCLUSIVE; 5402 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 5403 throw new ErrnoApiException("epoll_ctl", errno); 5404 5405 return UnregisterToken(this, fd, cb); 5406 } 5407 5408 /++ 5409 Adds a one-off callback that you can optionally rearm when it happens. 5410 +/ 5411 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb) { 5412 epoll_event event; 5413 event.data.ptr = cast(void*) cb; 5414 event.events = EPOLLIN | EPOLLONESHOT; 5415 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 5416 throw new ErrnoApiException("epoll_ctl", errno); 5417 5418 return RearmToken(true, this, fd, cb, EPOLLIN | EPOLLONESHOT); 5419 } 5420 5421 /++ 5422 Adds a one-off callback that you can optionally rearm when it happens. 5423 +/ 5424 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb) { 5425 epoll_event event; 5426 event.data.ptr = cast(void*) cb; 5427 event.events = EPOLLOUT | EPOLLONESHOT; 5428 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 5429 throw new ErrnoApiException("epoll_ctl", errno); 5430 5431 return RearmToken(false, this, fd, cb, EPOLLOUT | EPOLLONESHOT); 5432 } 5433 5434 private void unregisterFd(int fd) { 5435 epoll_event event; 5436 if(epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &event) == -1) 5437 throw new ErrnoApiException("epoll_ctl", errno); 5438 } 5439 5440 private void rearmFd(RearmToken token) { 5441 epoll_event event; 5442 event.data.ptr = cast(void*) token.cb; 5443 event.events = token.flags; 5444 if(epoll_ctl(epollfd, EPOLL_CTL_MOD, token.fd, &event) == -1) 5445 throw new ErrnoApiException("epoll_ctl", errno); 5446 } 5447 5448 // Disk files will have to be sent as messages to a worker to do the read and report back a completion packet. 5449 } 5450 5451 version(Arsd_core_kqueue) { 5452 // FIXME 5453 } 5454 5455 // cross platform adapters 5456 void setTimeout() {} 5457 void addFileOrDirectoryChangeListener(FilePath name, uint flags, bool recursive = false) {} 5458 } 5459 5460 // deduplication???????// 5461 bool postMessage(ThreadToRunIn destination, void delegate() code) { 5462 return false; 5463 } 5464 bool postMessage(ThreadToRunIn destination, Object message) { 5465 return false; 5466 } 5467 5468 /+ 5469 void main() { 5470 // FIXME: the offset doesn't seem to be done right 5471 auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation); 5472 file.write("hello", 10).waitForCompletion(); 5473 } 5474 +/ 5475 5476 // to test the mailboxes 5477 /+ 5478 void main() { 5479 /+ 5480 import std.stdio; 5481 Thread[4] pool; 5482 5483 bool shouldExit; 5484 5485 static int received; 5486 5487 static void tester() { 5488 received++; 5489 //writeln(cast(void*) Thread.getThis, " ", received); 5490 } 5491 5492 foreach(ref thread; pool) { 5493 thread = new Thread(() { 5494 getThisThreadEventLoop().run(() { 5495 return shouldExit; 5496 }); 5497 }); 5498 thread.start(); 5499 } 5500 5501 getThisThreadEventLoop(); // ensure it is all initialized before proceeding. FIXME: i should have an ensure initialized function i do on most the public apis. 5502 5503 int lol; 5504 5505 try 5506 foreach(i; 0 .. 6000) { 5507 CoreEventLoopImplementation.runInTaskRunnerQueue.enqueue(new CallbackHelper(&tester)); 5508 lol = cast(int) i; 5509 } 5510 catch(ArsdExceptionBase e) { 5511 Thread.sleep(50.msecs); 5512 writeln(e); 5513 writeln(lol); 5514 } 5515 5516 import core.stdc.stdlib; 5517 exit(0); 5518 5519 version(none) 5520 foreach(i; 0 .. 100) 5521 CoreEventLoopImplementation.runInTaskRunnerQueue.enqueue(new CallbackHelper(&tester)); 5522 5523 5524 foreach(ref thread; pool) { 5525 thread.join(); 5526 } 5527 +/ 5528 5529 5530 static int received; 5531 5532 static void tester() { 5533 received++; 5534 //writeln(cast(void*) Thread.getThis, " ", received); 5535 } 5536 5537 5538 5539 auto ev = cast(CoreEventLoopImplementation) getThisThreadEventLoop(); 5540 foreach(i; 0 .. 100) 5541 ev.thisThreadQueue.enqueue(new CallbackHelper(&tester)); 5542 foreach(i; 0 .. 100 / 16 + 1) 5543 ev.runOnce(); 5544 import std.conv; 5545 assert(received == 100, to!string(received)); 5546 5547 } 5548 +/ 5549 5550 /++ 5551 This is primarily a helper for the event queues. It is public in the hope it might be useful, 5552 but subject to change without notice; I will treat breaking it the same as if it is private. 5553 (That said, it is a simple little utility that does its job, so it is unlikely to change much. 5554 The biggest change would probably be letting it grow and changing from inline to dynamic array.) 5555 5556 It is a fixed-size ring buffer that synchronizes on a given object you give it in the constructor. 5557 5558 After enqueuing something, you should probably set an event to notify the other threads. This is left 5559 as an exercise to you (or another wrapper). 5560 +/ 5561 struct SynchronizedCircularBuffer(T, size_t maxSize = 128) { 5562 private T[maxSize] ring; 5563 private int front; 5564 private int back; 5565 5566 private Object synchronizedOn; 5567 5568 @disable this(); 5569 5570 /++ 5571 The Object's monitor is used to synchronize the methods in here. 5572 +/ 5573 this(Object synchronizedOn) { 5574 this.synchronizedOn = synchronizedOn; 5575 } 5576 5577 /++ 5578 Note the potential race condition between calling this and actually dequeuing something. You might 5579 want to acquire the lock on the object before calling this (nested synchronized things are allowed 5580 as long as the same thread is the one doing it). 5581 +/ 5582 bool isEmpty() { 5583 synchronized(this.synchronizedOn) { 5584 return front == back; 5585 } 5586 } 5587 5588 /++ 5589 Note the potential race condition between calling this and actually queuing something. 5590 +/ 5591 bool isFull() { 5592 synchronized(this.synchronizedOn) { 5593 return isFullUnsynchronized(); 5594 } 5595 } 5596 5597 private bool isFullUnsynchronized() nothrow const { 5598 return ((back + 1) % ring.length) == front; 5599 5600 } 5601 5602 /++ 5603 If this returns true, you should signal listening threads (with an event or a semaphore, 5604 depending on how you dequeue it). If it returns false, the queue was full and your thing 5605 was NOT added. You might wait and retry later (you could set up another event to signal it 5606 has been read and wait for that, or maybe try on a timer), or just fail and throw an exception 5607 or to abandon the message. 5608 +/ 5609 bool enqueue(T what) { 5610 synchronized(this.synchronizedOn) { 5611 if(isFullUnsynchronized()) 5612 return false; 5613 ring[(back++) % ring.length] = what; 5614 return true; 5615 } 5616 } 5617 5618 private T dequeueUnsynchronized() nothrow { 5619 assert(front != back); 5620 return ring[(front++) % ring.length]; 5621 } 5622 5623 /++ 5624 If you are using a semaphore to signal, you can call this once for each count of it 5625 and you can do that separately from this call (though they should be paired). 5626 5627 If you are using an event, you should use [dequeueSeveral] instead to drain it. 5628 +/ 5629 T dequeue() { 5630 synchronized(this.synchronizedOn) { 5631 return dequeueUnsynchronized(); 5632 } 5633 } 5634 5635 /++ 5636 Note that if you use a semaphore to signal waiting threads, you should probably not call this. 5637 5638 If you use a set/reset event, there's a potential race condition between the dequeue and event 5639 reset. This is why the `runInsideLockIfEmpty` delegate is there - when it is empty, before it 5640 unlocks, it will give you a chance to reset the event. Otherwise, it can remain set to indicate 5641 that there's still pending data in the queue. 5642 +/ 5643 T[] dequeueSeveral(return T[] buffer, scope void delegate() runInsideLockIfEmpty = null) { 5644 int pos; 5645 synchronized(this.synchronizedOn) { 5646 while(pos < buffer.length && front != back) { 5647 buffer[pos++] = dequeueUnsynchronized(); 5648 } 5649 if(front == back && runInsideLockIfEmpty !is null) 5650 runInsideLockIfEmpty(); 5651 } 5652 return buffer[0 .. pos]; 5653 } 5654 } 5655 5656 unittest { 5657 Object object = new Object(); 5658 auto queue = SynchronizedCircularBuffer!CallbackHelper(object); 5659 assert(queue.isEmpty); 5660 foreach(i; 0 .. queue.ring.length - 1) 5661 queue.enqueue(cast(CallbackHelper) cast(void*) i); 5662 assert(queue.isFull); 5663 5664 foreach(i; 0 .. queue.ring.length - 1) 5665 assert(queue.dequeue() is (cast(CallbackHelper) cast(void*) i)); 5666 assert(queue.isEmpty); 5667 5668 foreach(i; 0 .. queue.ring.length - 1) 5669 queue.enqueue(cast(CallbackHelper) cast(void*) i); 5670 assert(queue.isFull); 5671 5672 CallbackHelper[] buffer = new CallbackHelper[](300); 5673 auto got = queue.dequeueSeveral(buffer); 5674 assert(got.length == queue.ring.length - 1); 5675 assert(queue.isEmpty); 5676 foreach(i, item; got) 5677 assert(item is (cast(CallbackHelper) cast(void*) i)); 5678 5679 foreach(i; 0 .. 8) 5680 queue.enqueue(cast(CallbackHelper) cast(void*) i); 5681 buffer = new CallbackHelper[](4); 5682 got = queue.dequeueSeveral(buffer); 5683 assert(got.length == 4); 5684 foreach(i, item; got) 5685 assert(item is (cast(CallbackHelper) cast(void*) i)); 5686 got = queue.dequeueSeveral(buffer); 5687 assert(got.length == 4); 5688 foreach(i, item; got) 5689 assert(item is (cast(CallbackHelper) cast(void*) (i+4))); 5690 got = queue.dequeueSeveral(buffer); 5691 assert(got.length == 0); 5692 assert(queue.isEmpty); 5693 } 5694 5695 /++ 5696 5697 +/ 5698 enum ByteOrder { 5699 irrelevant, 5700 littleEndian, 5701 bigEndian, 5702 } 5703 5704 /++ 5705 A class to help write a stream of binary data to some target. 5706 5707 NOT YET FUNCTIONAL 5708 +/ 5709 class WritableStream { 5710 /++ 5711 5712 +/ 5713 this(size_t bufferSize) { 5714 this(new ubyte[](bufferSize)); 5715 } 5716 5717 /// ditto 5718 this(ubyte[] buffer) { 5719 this.buffer = buffer; 5720 } 5721 5722 /++ 5723 5724 +/ 5725 final void put(T)(T value, ByteOrder byteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5726 static if(T.sizeof == 8) 5727 ulong b; 5728 else static if(T.sizeof == 4) 5729 uint b; 5730 else static if(T.sizeof == 2) 5731 ushort b; 5732 else static if(T.sizeof == 1) 5733 ubyte b; 5734 else static assert(0, "unimplemented type, try using just the basic types"); 5735 5736 if(byteOrder == ByteOrder.irrelevant && T.sizeof > 1) 5737 throw new InvalidArgumentsException("byteOrder", "byte order must be specified for type " ~ T.stringof ~ " because it is bigger than one byte", "WritableStream.put", file, line); 5738 5739 final switch(byteOrder) { 5740 case ByteOrder.irrelevant: 5741 writeOneByte(b); 5742 break; 5743 case ByteOrder.littleEndian: 5744 foreach(i; 0 .. T.sizeof) { 5745 writeOneByte(b & 0xff); 5746 b >>= 8; 5747 } 5748 break; 5749 case ByteOrder.bigEndian: 5750 int amount = T.sizeof * 8 - 8; 5751 foreach(i; 0 .. T.sizeof) { 5752 writeOneByte((b >> amount) & 0xff); 5753 amount -= 8; 5754 } 5755 break; 5756 } 5757 } 5758 5759 /// ditto 5760 final void put(T : E[], E)(T value, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5761 foreach(item; value) 5762 put(item, elementByteOrder, file, line); 5763 } 5764 5765 /++ 5766 Performs a final flush() call, then marks the stream as closed, meaning no further data will be written to it. 5767 +/ 5768 void close() { 5769 isClosed_ = true; 5770 } 5771 5772 /++ 5773 Writes what is currently in the buffer to the target and waits for the target to accept it. 5774 Please note: if you are subclassing this to go to a different target 5775 +/ 5776 void flush() {} 5777 5778 /++ 5779 Returns true if either you closed it or if the receiving end closed their side, indicating they 5780 don't want any more data. 5781 +/ 5782 bool isClosed() { 5783 return isClosed_; 5784 } 5785 5786 // hasRoomInBuffer 5787 // canFlush 5788 // waitUntilCanFlush 5789 5790 // flushImpl 5791 // markFinished / close - tells the other end you're done 5792 5793 private final writeOneByte(ubyte value) { 5794 if(bufferPosition == buffer.length) 5795 flush(); 5796 5797 buffer[bufferPosition++] = value; 5798 } 5799 5800 5801 private { 5802 ubyte[] buffer; 5803 int bufferPosition; 5804 bool isClosed_; 5805 } 5806 } 5807 5808 /++ 5809 A stream can be used by just one task at a time, but one task can consume multiple streams. 5810 5811 Streams may be populated by async sources (in which case they must be called from a fiber task), 5812 from a function generating the data on demand (including an input range), from memory, or from a synchronous file. 5813 5814 A stream of heterogeneous types is compatible with input ranges. 5815 5816 It reads binary data. 5817 +/ 5818 version(HasThread) class ReadableStream { 5819 5820 this() { 5821 5822 } 5823 5824 /++ 5825 Gets data of the specified type `T` off the stream. The byte order of the T on the stream must be specified unless it is irrelevant (e.g. single byte entries). 5826 5827 --- 5828 // get an int out of a big endian stream 5829 int i = stream.get!int(ByteOrder.bigEndian); 5830 5831 // get i bytes off the stream 5832 ubyte[] data = stream.get!(ubyte[])(i); 5833 --- 5834 +/ 5835 final T get(T)(ByteOrder byteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5836 if(byteOrder == ByteOrder.irrelevant && T.sizeof > 1) 5837 throw new InvalidArgumentsException("byteOrder", "byte order must be specified for type " ~ T.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 5838 5839 // FIXME: what if it is a struct? 5840 5841 while(bufferedLength() < T.sizeof) 5842 waitForAdditionalData(); 5843 5844 static if(T.sizeof == 1) { 5845 ubyte ret = consumeOneByte(); 5846 return *cast(T*) &ret; 5847 } else { 5848 static if(T.sizeof == 8) 5849 ulong ret; 5850 else static if(T.sizeof == 4) 5851 uint ret; 5852 else static if(T.sizeof == 2) 5853 ushort ret; 5854 else static assert(0, "unimplemented type, try using just the basic types"); 5855 5856 if(byteOrder == ByteOrder.littleEndian) { 5857 typeof(ret) buffer; 5858 foreach(b; 0 .. T.sizeof) { 5859 buffer = consumeOneByte(); 5860 buffer <<= T.sizeof * 8 - 8; 5861 5862 ret >>= 8; 5863 ret |= buffer; 5864 } 5865 } else { 5866 foreach(b; 0 .. T.sizeof) { 5867 ret <<= 8; 5868 ret |= consumeOneByte(); 5869 } 5870 } 5871 5872 return *cast(T*) &ret; 5873 } 5874 } 5875 5876 /// ditto 5877 final T get(T : E[], E)(size_t length, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5878 if(elementByteOrder == ByteOrder.irrelevant && E.sizeof > 1) 5879 throw new InvalidArgumentsException("elementByteOrder", "byte order must be specified for type " ~ E.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 5880 5881 // if the stream is closed before getting the length or the terminator, should we send partial stuff 5882 // or just throw? 5883 5884 while(bufferedLength() < length * E.sizeof) 5885 waitForAdditionalData(); 5886 5887 T ret; 5888 5889 ret.length = length; 5890 5891 if(false && elementByteOrder == ByteOrder.irrelevant) { 5892 // ret[] = 5893 // FIXME: can prolly optimize 5894 } else { 5895 foreach(i; 0 .. length) 5896 ret[i] = get!E(elementByteOrder); 5897 } 5898 5899 return ret; 5900 5901 } 5902 5903 /// ditto 5904 final T get(T : E[], E)(scope bool delegate(E e) isTerminatingSentinel, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5905 if(byteOrder == ByteOrder.irrelevant && E.sizeof > 1) 5906 throw new InvalidArgumentsException("elementByteOrder", "byte order must be specified for type " ~ E.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 5907 5908 assert(0, "Not implemented"); 5909 } 5910 5911 /++ 5912 5913 +/ 5914 bool isClosed() { 5915 return isClosed_; 5916 } 5917 5918 // Control side of things 5919 5920 private bool isClosed_; 5921 5922 /++ 5923 Feeds data into the stream, which can be consumed by `get`. If a task is waiting for more 5924 data to satisfy its get requests, this will trigger those tasks to resume. 5925 5926 If you feed it empty data, it will mark the stream as closed. 5927 +/ 5928 void feedData(ubyte[] data) { 5929 if(data.length == 0) 5930 isClosed_ = true; 5931 5932 currentBuffer = data; 5933 // this is a borrowed buffer, so we won't keep the reference long term 5934 scope(exit) 5935 currentBuffer = null; 5936 5937 if(waitingTask !is null) { 5938 waitingTask.call(); 5939 } 5940 } 5941 5942 /++ 5943 You basically have to use this thing from a task 5944 +/ 5945 protected void waitForAdditionalData() { 5946 Fiber task = Fiber.getThis; 5947 5948 assert(task !is null); 5949 5950 if(waitingTask !is null && waitingTask !is task) 5951 throw new ArsdException!"streams can only have one waiting task"; 5952 5953 // copy any pending data in our buffer to the longer-term buffer 5954 if(currentBuffer.length) 5955 leftoverBuffer ~= currentBuffer; 5956 5957 waitingTask = task; 5958 task.yield(); 5959 } 5960 5961 private Fiber waitingTask; 5962 private ubyte[] leftoverBuffer; 5963 private ubyte[] currentBuffer; 5964 5965 private size_t bufferedLength() { 5966 return leftoverBuffer.length + currentBuffer.length; 5967 } 5968 5969 private ubyte consumeOneByte() { 5970 ubyte b; 5971 if(leftoverBuffer.length) { 5972 b = leftoverBuffer[0]; 5973 leftoverBuffer = leftoverBuffer[1 .. $]; 5974 } else if(currentBuffer.length) { 5975 b = currentBuffer[0]; 5976 currentBuffer = currentBuffer[1 .. $]; 5977 } else { 5978 assert(0, "consuming off an empty buffer is impossible"); 5979 } 5980 5981 return b; 5982 } 5983 } 5984 5985 // FIXME: do a stringstream too 5986 5987 unittest { 5988 auto stream = new ReadableStream(); 5989 5990 int position; 5991 char[16] errorBuffer; 5992 5993 auto fiber = new Fiber(() { 5994 position = 1; 5995 int a = stream.get!int(ByteOrder.littleEndian); 5996 assert(a == 10, intToString(a, errorBuffer[])); 5997 position = 2; 5998 ubyte b = stream.get!ubyte; 5999 assert(b == 33); 6000 position = 3; 6001 6002 // ubyte[] c = stream.get!(ubyte[])(3); 6003 // int[] d = stream.get!(int[])(3); 6004 }); 6005 6006 fiber.call(); 6007 assert(position == 1); 6008 stream.feedData([10, 0, 0, 0]); 6009 assert(position == 2); 6010 stream.feedData([33]); 6011 assert(position == 3); 6012 6013 // stream.feedData([1,2,3]); 6014 // stream.feedData([1,2,3,4,1,2,3,4,1,2,3,4]); 6015 } 6016 6017 /++ 6018 UNSTABLE, NOT FULLY IMPLEMENTED. DO NOT USE YET. 6019 6020 You might use this like: 6021 6022 --- 6023 auto proc = new ExternalProcess(); 6024 auto stdoutStream = new ReadableStream(); 6025 6026 // to use a stream you can make one and have a task consume it 6027 runTask({ 6028 while(!stdoutStream.isClosed) { 6029 auto line = stdoutStream.get!string(e => e == '\n'); 6030 } 6031 }); 6032 6033 // then make the process feed into the stream 6034 proc.onStdoutAvailable = (got) { 6035 stdoutStream.feedData(got); // send it to the stream for processing 6036 stdout.rawWrite(got); // forward it through to our own thing 6037 // could also append it to a buffer to return it on complete 6038 }; 6039 proc.start(); 6040 --- 6041 6042 Please note that this does not currently and I have no plans as of this writing to add support for any kind of direct file descriptor passing. It always pipes them back to the parent for processing. If you don't want this, call the lower level functions yourself; the reason this class is here is to aid integration in the arsd.core event loop. Of course, I might change my mind on this. 6043 6044 Bugs: 6045 Not implemented at all on Windows yet. 6046 +/ 6047 class ExternalProcess /*: AsyncOperationRequest*/ { 6048 6049 private static version(Posix) { 6050 __gshared ExternalProcess[pid_t] activeChildren; 6051 6052 void recordChildCreated(pid_t pid, ExternalProcess proc) { 6053 synchronized(typeid(ExternalProcess)) { 6054 activeChildren[pid] = proc; 6055 } 6056 } 6057 6058 void recordChildTerminated(pid_t pid, int status) { 6059 synchronized(typeid(ExternalProcess)) { 6060 if(pid in activeChildren) { 6061 auto ac = activeChildren[pid]; 6062 ac.completed = true; 6063 ac.status = status; 6064 activeChildren.remove(pid); 6065 } 6066 } 6067 } 6068 } 6069 6070 // FIXME: config to pass through a shell or not 6071 6072 /++ 6073 This is the native version for Windows. 6074 +/ 6075 this(string program, string commandLine) { 6076 version(Posix) { 6077 assert(0, "not implemented command line to posix args yet"); 6078 } 6079 else throw new NotYetImplementedException(); 6080 } 6081 6082 this(string commandLine) { 6083 version(Posix) { 6084 assert(0, "not implemented command line to posix args yet"); 6085 } 6086 else throw new NotYetImplementedException(); 6087 } 6088 6089 this(string[] args) { 6090 version(Posix) { 6091 this.program = FilePath(args[0]); 6092 this.args = args; 6093 } 6094 else throw new NotYetImplementedException(); 6095 } 6096 6097 /++ 6098 This is the native version for Posix. 6099 +/ 6100 this(FilePath program, string[] args) { 6101 version(Posix) { 6102 this.program = program; 6103 this.args = args; 6104 } 6105 else throw new NotYetImplementedException(); 6106 } 6107 6108 // you can modify these before calling start 6109 int stdoutBufferSize = 32 * 1024; 6110 int stderrBufferSize = 8 * 1024; 6111 6112 void start() { 6113 version(Posix) { 6114 int ret; 6115 6116 int[2] stdinPipes; 6117 ret = pipe(stdinPipes); 6118 if(ret == -1) 6119 throw new ErrnoApiException("stdin pipe", errno); 6120 6121 scope(failure) { 6122 close(stdinPipes[0]); 6123 close(stdinPipes[1]); 6124 } 6125 6126 stdinFd = stdinPipes[1]; 6127 6128 int[2] stdoutPipes; 6129 ret = pipe(stdoutPipes); 6130 if(ret == -1) 6131 throw new ErrnoApiException("stdout pipe", errno); 6132 6133 scope(failure) { 6134 close(stdoutPipes[0]); 6135 close(stdoutPipes[1]); 6136 } 6137 6138 stdoutFd = stdoutPipes[0]; 6139 6140 int[2] stderrPipes; 6141 ret = pipe(stderrPipes); 6142 if(ret == -1) 6143 throw new ErrnoApiException("stderr pipe", errno); 6144 6145 scope(failure) { 6146 close(stderrPipes[0]); 6147 close(stderrPipes[1]); 6148 } 6149 6150 stderrFd = stderrPipes[0]; 6151 6152 6153 int[2] errorReportPipes; 6154 ret = pipe(errorReportPipes); 6155 if(ret == -1) 6156 throw new ErrnoApiException("error reporting pipe", errno); 6157 6158 scope(failure) { 6159 close(errorReportPipes[0]); 6160 close(errorReportPipes[1]); 6161 } 6162 6163 setCloExec(errorReportPipes[0]); 6164 setCloExec(errorReportPipes[1]); 6165 6166 auto forkRet = fork(); 6167 if(forkRet == -1) 6168 throw new ErrnoApiException("fork", errno); 6169 6170 if(forkRet == 0) { 6171 // child side 6172 6173 // FIXME can we do more error checking that is actually useful here? 6174 // these operations are virtually guaranteed to succeed given the setup anyway. 6175 6176 // FIXME pty too 6177 6178 void fail(int step) { 6179 import core.stdc.errno; 6180 auto code = errno; 6181 6182 // report the info back to the parent then exit 6183 6184 int[2] msg = [step, code]; 6185 auto ret = write(errorReportPipes[1], msg.ptr, msg.sizeof); 6186 6187 // but if this fails there's not much we can do... 6188 6189 import core.stdc.stdlib; 6190 exit(1); 6191 } 6192 6193 // dup2 closes the fd it is replacing automatically 6194 dup2(stdinPipes[0], 0); 6195 dup2(stdoutPipes[1], 1); 6196 dup2(stderrPipes[1], 2); 6197 6198 // don't need either of the original pipe fds anymore 6199 close(stdinPipes[0]); 6200 close(stdinPipes[1]); 6201 close(stdoutPipes[0]); 6202 close(stdoutPipes[1]); 6203 close(stderrPipes[0]); 6204 close(stderrPipes[1]); 6205 6206 // the error reporting pipe will be closed upon exec since we set cloexec before fork 6207 // and everything else should have cloexec set too hopefully. 6208 6209 if(beforeExec) 6210 beforeExec(); 6211 6212 // i'm not sure that a fully-initialized druntime is still usable 6213 // after a fork(), so i'm gonna stick to the C lib in here. 6214 6215 const(char)* file = mallocedStringz(program.path).ptr; 6216 if(file is null) 6217 fail(1); 6218 const(char)*[] argv = mallocSlice!(const(char)*)(args.length + 1); 6219 if(argv is null) 6220 fail(2); 6221 foreach(idx, arg; args) { 6222 argv[idx] = mallocedStringz(args[idx]).ptr; 6223 if(argv[idx] is null) 6224 fail(3); 6225 } 6226 argv[args.length] = null; 6227 6228 auto rete = execvp/*e*/(file, argv.ptr/*, envp*/); 6229 if(rete == -1) { 6230 fail(4); 6231 } else { 6232 // unreachable code, exec never returns if it succeeds 6233 assert(0); 6234 } 6235 } else { 6236 pid = forkRet; 6237 6238 recordChildCreated(pid, this); 6239 6240 // close our copy of the write side of the error reporting pipe 6241 // so the read will immediately give eof when the fork closes it too 6242 ErrnoEnforce!close(errorReportPipes[1]); 6243 6244 int[2] msg; 6245 // this will block to wait for it to actually either start up or fail to exec (which should be near instant) 6246 auto val = read(errorReportPipes[0], msg.ptr, msg.sizeof); 6247 6248 if(val == -1) 6249 throw new ErrnoApiException("read error report", errno); 6250 6251 if(val == msg.sizeof) { 6252 // error happened 6253 // FIXME: keep the step part of the error report too 6254 throw new ErrnoApiException("exec", msg[1]); 6255 } else if(val == 0) { 6256 // pipe closed, meaning exec succeeded 6257 } else { 6258 assert(0); // never supposed to happen 6259 } 6260 6261 // set the ones we keep to close upon future execs 6262 // FIXME should i set NOBLOCK at this time too? prolly should 6263 setCloExec(stdinPipes[1]); 6264 setCloExec(stdoutPipes[0]); 6265 setCloExec(stderrPipes[0]); 6266 6267 // and close the others 6268 ErrnoEnforce!close(stdinPipes[0]); 6269 ErrnoEnforce!close(stdoutPipes[1]); 6270 ErrnoEnforce!close(stderrPipes[1]); 6271 6272 ErrnoEnforce!close(errorReportPipes[0]); 6273 6274 // and now register the ones we need to read with the event loop so it can call the callbacks 6275 // also need to listen to SIGCHLD to queue up the terminated callback. FIXME 6276 6277 stdoutUnregisterToken = getThisThreadEventLoop().addCallbackOnFdReadable(stdoutFd, new CallbackHelper(&stdoutReadable)); 6278 stderrUnregisterToken = getThisThreadEventLoop().addCallbackOnFdReadable(stderrFd, new CallbackHelper(&stderrReadable)); 6279 } 6280 } 6281 } 6282 6283 private version(Posix) { 6284 import core.sys.posix.unistd; 6285 import core.sys.posix.fcntl; 6286 6287 int stdinFd = -1; 6288 int stdoutFd = -1; 6289 int stderrFd = -1; 6290 6291 ICoreEventLoop.UnregisterToken stdoutUnregisterToken; 6292 ICoreEventLoop.UnregisterToken stderrUnregisterToken; 6293 6294 pid_t pid = -1; 6295 6296 public void delegate() beforeExec; 6297 6298 FilePath program; 6299 string[] args; 6300 6301 void stdoutReadable() { 6302 if(stdoutReadBuffer is null) 6303 stdoutReadBuffer = new ubyte[](stdoutBufferSize); 6304 auto ret = read(stdoutFd, stdoutReadBuffer.ptr, stdoutReadBuffer.length); 6305 if(ret == -1) 6306 throw new ErrnoApiException("read", errno); 6307 if(onStdoutAvailable) { 6308 onStdoutAvailable(stdoutReadBuffer[0 .. ret]); 6309 } 6310 6311 if(ret == 0) { 6312 stdoutUnregisterToken.unregister(); 6313 6314 close(stdoutFd); 6315 stdoutFd = -1; 6316 } 6317 } 6318 6319 void stderrReadable() { 6320 if(stderrReadBuffer is null) 6321 stderrReadBuffer = new ubyte[](stderrBufferSize); 6322 auto ret = read(stderrFd, stderrReadBuffer.ptr, stderrReadBuffer.length); 6323 if(ret == -1) 6324 throw new ErrnoApiException("read", errno); 6325 if(onStderrAvailable) { 6326 onStderrAvailable(stderrReadBuffer[0 .. ret]); 6327 } 6328 6329 if(ret == 0) { 6330 stderrUnregisterToken.unregister(); 6331 6332 close(stderrFd); 6333 stderrFd = -1; 6334 } 6335 } 6336 } 6337 6338 private ubyte[] stdoutReadBuffer; 6339 private ubyte[] stderrReadBuffer; 6340 6341 void waitForCompletion() { 6342 getThisThreadEventLoop().run(&this.isComplete); 6343 } 6344 6345 bool isComplete() { 6346 return completed; 6347 } 6348 6349 bool completed; 6350 int status = int.min; 6351 6352 /++ 6353 If blocking, it will block the current task until the write succeeds. 6354 6355 Write `null` as data to close the pipe. Once the pipe is closed, you must not try to write to it again. 6356 +/ 6357 void writeToStdin(in void[] data) { 6358 version(Posix) { 6359 if(data is null) { 6360 close(stdinFd); 6361 stdinFd = -1; 6362 } else { 6363 // FIXME: check the return value again and queue async writes 6364 auto ret = write(stdinFd, data.ptr, data.length); 6365 if(ret == -1) 6366 throw new ErrnoApiException("write", errno); 6367 } 6368 } 6369 6370 } 6371 6372 void delegate(ubyte[] got) onStdoutAvailable; 6373 void delegate(ubyte[] got) onStderrAvailable; 6374 void delegate(int code) onTermination; 6375 6376 // pty? 6377 } 6378 6379 // FIXME: comment this out 6380 /+ 6381 unittest { 6382 auto proc = new ExternalProcess(FilePath("/bin/cat"), ["/bin/cat"]); 6383 6384 getThisThreadEventLoop(); // initialize it 6385 6386 int c = 0; 6387 proc.onStdoutAvailable = delegate(ubyte[] got) { 6388 if(c == 0) 6389 assert(cast(string) got == "hello!"); 6390 else 6391 assert(got.length == 0); 6392 // import std.stdio; writeln(got); 6393 c++; 6394 }; 6395 6396 proc.start(); 6397 6398 assert(proc.pid != -1); 6399 6400 6401 import std.stdio; 6402 Thread[4] pool; 6403 6404 bool shouldExit; 6405 6406 static int received; 6407 6408 proc.writeToStdin("hello!"); 6409 proc.writeToStdin(null); // closes the pipe 6410 6411 proc.waitForCompletion(); 6412 6413 assert(proc.status == 0); 6414 6415 assert(c == 2); 6416 6417 // writeln("here"); 6418 } 6419 +/ 6420 6421 // to test the thundering herd on signal handling 6422 version(none) 6423 unittest { 6424 Thread[4] pool; 6425 foreach(ref thread; pool) { 6426 thread = new class Thread { 6427 this() { 6428 super({ 6429 int count; 6430 getThisThreadEventLoop().run(() { 6431 if(count > 4) return true; 6432 count++; 6433 return false; 6434 }); 6435 }); 6436 } 6437 }; 6438 thread.start(); 6439 } 6440 foreach(ref thread; pool) { 6441 thread.join(); 6442 } 6443 } 6444 6445 /+ 6446 ================= 6447 STDIO REPLACEMENT 6448 ================= 6449 +/ 6450 6451 private void appendToBuffer(ref char[] buffer, ref int pos, scope const(char)[] what) { 6452 auto required = pos + what.length; 6453 if(buffer.length < required) 6454 buffer.length = required; 6455 buffer[pos .. pos + what.length] = what[]; 6456 pos += what.length; 6457 } 6458 6459 private void appendToBuffer(ref char[] buffer, ref int pos, long what) { 6460 if(buffer.length < pos + 16) 6461 buffer.length = pos + 16; 6462 auto sliced = intToString(what, buffer[pos .. $]); 6463 pos += sliced.length; 6464 } 6465 6466 /++ 6467 A `writeln` that actually works, at least for some basic types. 6468 6469 It works correctly on Windows, using the correct functions to write unicode to the console. even allocating a console if needed. If the output has been redirected to a file or pipe, it writes UTF-8. 6470 6471 This always does text. See also WritableStream and WritableTextStream when they are implemented. 6472 +/ 6473 void writeln(T...)(T t) { 6474 char[256] bufferBacking; 6475 char[] buffer = bufferBacking[]; 6476 int pos; 6477 6478 foreach(arg; t) { 6479 static if(is(typeof(arg) : const char[])) { 6480 appendToBuffer(buffer, pos, arg); 6481 } else static if(is(typeof(arg) : stringz)) { 6482 appendToBuffer(buffer, pos, arg.borrow); 6483 } else static if(is(typeof(arg) : long)) { 6484 appendToBuffer(buffer, pos, arg); 6485 } else static if(is(typeof(arg.toString()) : const char[])) { 6486 appendToBuffer(buffer, pos, arg.toString()); 6487 } else { 6488 appendToBuffer(buffer, pos, "<" ~ typeof(arg).stringof ~ ">"); 6489 } 6490 } 6491 6492 appendToBuffer(buffer, pos, "\n"); 6493 6494 actuallyWriteToStdout(buffer[0 .. pos]); 6495 } 6496 6497 private void actuallyWriteToStdout(scope char[] buffer) @trusted { 6498 6499 version(UseStdioWriteln) 6500 { 6501 import std.stdio; 6502 writeln(buffer); 6503 } 6504 else version(Windows) { 6505 import core.sys.windows.wincon; 6506 6507 auto hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 6508 if(hStdOut == null || hStdOut == INVALID_HANDLE_VALUE) { 6509 AllocConsole(); 6510 hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 6511 } 6512 6513 if(GetFileType(hStdOut) == FILE_TYPE_CHAR) { 6514 wchar[256] wbuffer; 6515 auto toWrite = makeWindowsString(buffer, wbuffer, WindowsStringConversionFlags.convertNewLines); 6516 6517 DWORD written; 6518 WriteConsoleW(hStdOut, toWrite.ptr, cast(DWORD) toWrite.length, &written, null); 6519 } else { 6520 DWORD written; 6521 WriteFile(hStdOut, buffer.ptr, cast(DWORD) buffer.length, &written, null); 6522 } 6523 } else { 6524 import unix = core.sys.posix.unistd; 6525 unix.write(1, buffer.ptr, buffer.length); 6526 } 6527 } 6528 6529 /+ 6530 6531 STDIO 6532 6533 /++ 6534 Please note using this will create a compile-time dependency on [arsd.terminal] 6535 6536 6537 6538 so my writeln replacement: 6539 6540 1) if the std output handle is null, alloc one 6541 2) if it is a character device, write out the proper Unicode text. 6542 3) otherwise write out UTF-8.... maybe with a BOM but maybe not. it is tricky to know what the other end of a pipe expects... 6543 [8:15 AM] 6544 im actually tempted to make the write binary to stdout functions throw an exception if it is a character console / interactive terminal instead of letting you spam it right out 6545 [8:16 AM] 6546 of course you can still cheat by casting binary data to string and using the write string function (and this might be appropriate sometimes) but there kinda is a legit difference between a text output and a binary output device 6547 6548 Stdout can represent either 6549 6550 +/ 6551 void writeln(){} { 6552 6553 } 6554 6555 stderr? 6556 6557 /++ 6558 Please note using this will create a compile-time dependency on [arsd.terminal] 6559 6560 It can be called from a task. 6561 6562 It works correctly on Windows and is user friendly on Linux (using arsd.terminal.getline) 6563 while also working if stdin has been redirected (where arsd.terminal itself would throw) 6564 6565 6566 so say you run a program on an interactive terminal. the program tries to open the stdin binary stream 6567 6568 instead of throwing, the prompt could change to indicate the binary data is expected and you can feed it in either by typing it up,,,, or running some command like maybe <file.bin to have the library do what the shell would have done and feed that to the rest of the program 6569 6570 +/ 6571 string readln()() { 6572 6573 } 6574 6575 6576 // if using stdio as a binary output thing you can pretend it is a file w/ stream capability 6577 struct File { 6578 WritableStream ostream; 6579 ReadableStream istream; 6580 6581 ulong tell; 6582 void seek(ulong to) {} 6583 6584 void sync(); 6585 void close(); 6586 } 6587 6588 // these are a bit special because if it actually is an interactive character device, it might be different than other files and even different than other pipes. 6589 WritableStream stdoutStream() { return null; } 6590 WritableStream stderrStream() { return null; } 6591 ReadableStream stdinStream() { return null; } 6592 6593 +/ 6594 6595 6596 /+ 6597 6598 6599 /+ 6600 Druntime appears to have stuff for darwin, freebsd. I might have to add some for openbsd here and maybe netbsd if i care to test it. 6601 +/ 6602 6603 /+ 6604 6605 arsd_core_init(number_of_worker_threads) 6606 6607 Building-block things wanted for the event loop integration: 6608 * ui 6609 * windows 6610 * terminal / console 6611 * generic 6612 * adopt fd 6613 * adopt windows handle 6614 * shared lib 6615 * load 6616 * timers (relative and real time) 6617 * create 6618 * update 6619 * cancel 6620 * file/directory watches 6621 * file created 6622 * file deleted 6623 * file modified 6624 * file ops 6625 * open 6626 * close 6627 * read 6628 * write 6629 * seek 6630 * sendfile on linux, TransmitFile on Windows 6631 * let completion handlers run in the io worker thread instead of signaling back 6632 * pipe ops (anonymous or named) 6633 * create 6634 * read 6635 * write 6636 * get info about other side of the pipe 6637 * network ops (stream + datagram, ip, ipv6, unix) 6638 * address look up 6639 * connect 6640 * start tls 6641 * listen 6642 * send 6643 * receive 6644 * get peer info 6645 * process ops 6646 * spawn 6647 * notifications when it is terminated or fork or execs 6648 * send signal 6649 * i/o pipes 6650 * thread ops (isDaemon?) 6651 * spawn 6652 * talk to its event loop 6653 * termination notification 6654 * signals 6655 * ctrl+c is the only one i really care about but the others might be made available too. sigchld needs to be done as an impl detail of process ops. 6656 * custom messages 6657 * should be able to send messages from finalizers... 6658 6659 * want to make sure i can stream stuff on top of it all too. 6660 6661 ======== 6662 6663 These things all refer back to a task-local thing that queues the tasks. If it is a fiber, it uses that 6664 and if it is a thread it uses that... 6665 6666 tls IArsdCoreEventLoop curentTaskInterface; // this yields on the wait for calls. the fiber swapper will swap this too. 6667 tls IArsdCoreEventLoop currentThreadInterface; // this blocks on the event loop 6668 6669 shared IArsdCoreEventLoop currentProcessInterface; // this dispatches to any available thread 6670 +/ 6671 6672 6673 /+ 6674 You might have configurable tasks that do not auto-start, e.g. httprequest. maybe @mustUse on those 6675 6676 then some that do auto-start, e.g. setTimeout 6677 6678 6679 timeouts: duration, MonoTime, or SysTime? duration is just a timer monotime auto-adjusts the when, systime sets a real time timerfd 6680 6681 tasks can be set to: 6682 thread affinity - this, any, specific reference 6683 reports to - defaults to this, can also pass down a parent reference. if reports to dies, all its subordinates are cancelled. 6684 6685 6686 you can send a message to a task... maybe maybe just to a task runner (which is itself a task?) 6687 6688 auto file = readFile(x); 6689 auto timeout = setTimeout(y); 6690 auto completed = waitForFirstToCompleteThenCancelOthers(file, timeout); 6691 if(completed == 0) { 6692 file.... 6693 } else { 6694 timeout.... 6695 } 6696 6697 /+ 6698 A task will run on a thread (with possible migration), and report to a task. 6699 +/ 6700 6701 // a compute task is run on a helper thread 6702 auto task = computeTask((shared(bool)* cancellationRequested) { 6703 // or pass in a yield thing... prolly a TaskController which has cancellationRequested and yield controls as well as send message to parent (sync or async) 6704 6705 // you'd periodically send messages back to the parent 6706 }, RunOn.AnyAvailable, Affinity.CanMigrate); 6707 6708 auto task = Task((TaskController controller) { 6709 foreach(x, 0 .. 1000) { 6710 if(x % 10 == 0) 6711 controller.yield(); // periodically yield control, which also checks for cancellation for us 6712 // do some work 6713 6714 controller.sendMessage(...); 6715 controller.sendProgress(x); // yields it for a foreach stream kind of thing 6716 } 6717 6718 return something; // automatically sends the something as the result in a TaskFinished message 6719 }); 6720 6721 foreach(item; task) // waitsForProgress, sendProgress sends an item and the final return sends an item 6722 {} 6723 6724 6725 see ~/test/task.d 6726 6727 // an io task is run locally via the event loops 6728 auto task2 = ioTask(() { 6729 6730 }); 6731 6732 6733 6734 waitForEvent 6735 +/ 6736 6737 /+ 6738 Most functions should prolly take a thread arg too, which defaults 6739 to this thread, but you can also pass it a reference, or a "any available" thing. 6740 6741 This can be a ufcs overload 6742 +/ 6743 6744 interface SemiSynchronousTask { 6745 6746 } 6747 6748 struct TimeoutCompletionResult { 6749 bool completed; 6750 6751 bool opCast(T : bool)() { 6752 return completed; 6753 } 6754 } 6755 6756 struct Timeout { 6757 void reschedule(Duration when) { 6758 6759 } 6760 6761 void cancel() { 6762 6763 } 6764 6765 TimeoutCompletionResult waitForCompletion() { 6766 return TimeoutCompletionResult(false); 6767 } 6768 } 6769 6770 Timeout setTimeout(void delegate() dg, int msecs, int permittedJitter = 20) { 6771 return Timeout.init; 6772 } 6773 6774 void clearTimeout(Timeout timeout) { 6775 timeout.cancel(); 6776 } 6777 6778 void createInterval() {} 6779 void clearInterval() {} 6780 6781 /++ 6782 Schedules a task at the given wall clock time. 6783 +/ 6784 void scheduleTask() {} 6785 6786 struct IoOperationCompletionResult { 6787 enum Status { 6788 cancelled, 6789 completed 6790 } 6791 6792 Status status; 6793 6794 int error; 6795 int bytesWritten; 6796 6797 bool opCast(T : bool)() { 6798 return status == Status.completed; 6799 } 6800 } 6801 6802 struct IoOperation { 6803 void cancel() {} 6804 6805 IoOperationCompletionResult waitForCompletion() { 6806 return IoOperationCompletionResult.init; 6807 } 6808 6809 // could contain a scoped class in here too so it stack allocated 6810 } 6811 6812 // Should return both the object and the index in the array! 6813 Result waitForFirstToComplete(Operation[]...) {} 6814 6815 IoOperation read(IoHandle handle, ubyte[] buffer 6816 6817 /+ 6818 class IoOperation {} 6819 6820 // an io operation and its buffer must not be modified or freed 6821 // in between a call to enqueue and a call to waitForCompletion 6822 // if you used the whenComplete callback, make sure it is NOT gc'd or scope thing goes out of scope in the mean time 6823 // if its dtor runs, it'd be forced to be cancelled... 6824 6825 scope IoOperation op = new IoOperation(buffer_size); 6826 op.start(); 6827 op.waitForCompletion(); 6828 +/ 6829 6830 /+ 6831 will want: 6832 read, write 6833 send, recv 6834 6835 cancel 6836 6837 open file, open (named or anonymous) pipe, open process 6838 connect, accept 6839 SSL 6840 close 6841 6842 postEvent 6843 postAPC? like run in gui thread / async 6844 waitForEvent ? needs to handle a timeout and a cancellation. would only work in the fiber task api. 6845 6846 waitForSuccess 6847 6848 interrupt handler 6849 6850 onPosixReadReadiness 6851 onPosixWriteReadiness 6852 6853 onWindowsHandleReadiness 6854 - but they're one-offs so you gotta reregister for each event 6855 +/ 6856 6857 6858 6859 /+ 6860 arsd.core.uda 6861 6862 you define a model struct with the types you want to extract 6863 6864 you get it with like Model extract(Model, UDAs...)(Model default) 6865 6866 defaultModel!alias > defaultModel!Type(defaultModel("identifier")) 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 so while i laid there sleep deprived i did think a lil more on some uda stuff. it isn't especially novel but a combination of a few other techniques 6878 6879 you might be like 6880 6881 struct MyUdas { 6882 DbName name; 6883 DbIgnore ignore; 6884 } 6885 6886 elsewhere 6887 6888 foreach(alias; allMembers) { 6889 auto udas = getUdas!(MyUdas, __traits(getAttributes, alias))(MyUdas(DbName(__traits(identifier, alias)))); 6890 } 6891 6892 6893 so you pass the expected type and the attributes as the template params, then the runtime params are the default values for the given types 6894 6895 so what the thing does essentially is just sets the values of the given thing to the udas based on type then returns the modified instance 6896 6897 so the end result is you keep the last ones. it wouldn't report errors if multiple things added but it p simple to understand, simple to document (even though the default values are not in the struct itself, you can put ddocs in them), and uses the tricks to minimize generated code size 6898 +/ 6899 6900 +/ 6901 6902 package(arsd) version(Windows) extern(Windows) { 6903 BOOL CancelIoEx(HANDLE, LPOVERLAPPED); 6904 6905 struct WSABUF { 6906 ULONG len; 6907 ubyte* buf; 6908 } 6909 alias LPWSABUF = WSABUF*; 6910 6911 // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-wsaoverlapped 6912 // "The WSAOVERLAPPED structure is compatible with the Windows OVERLAPPED structure." 6913 // so ima lie here in the bindings. 6914 6915 int WSASend(SOCKET, LPWSABUF, DWORD, LPDWORD, DWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 6916 int WSASendTo(SOCKET, LPWSABUF, DWORD, LPDWORD, DWORD, const sockaddr*, int, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 6917 6918 int WSARecv(SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 6919 int WSARecvFrom(SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, sockaddr*, LPINT, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 6920 } 6921 6922 package(arsd) version(OSXCocoa) { 6923 6924 /* Copy/paste chunk from Jacob Carlborg { */ 6925 // from https://raw.githubusercontent.com/jacob-carlborg/druntime/550edd0a64f0eb2c4f35d3ec3d88e26b40ac779e/src/core/stdc/clang_block.d 6926 // with comments stripped (see docs in the original link), code reformatted, and some names changed to avoid potential conflicts 6927 6928 import core.stdc.config; 6929 struct ObjCBlock(R = void, Params...) { 6930 private: 6931 alias extern(C) R function(ObjCBlock*, Params) Invoke; 6932 6933 void* isa; 6934 int flags; 6935 int reserved = 0; 6936 Invoke invoke; 6937 Descriptor* descriptor; 6938 6939 // Imported variables go here 6940 R delegate(Params) dg; 6941 6942 this(void* isa, int flags, Invoke invoke, R delegate(Params) dg) { 6943 this.isa = isa; 6944 this.flags = flags; 6945 this.invoke = invoke; 6946 this.dg = dg; 6947 this.descriptor = &.objcblock_descriptor; 6948 } 6949 } 6950 ObjCBlock!(R, Params) block(R, Params...)(R delegate(Params) dg) { 6951 static if (Params.length == 0) 6952 enum flags = 0x50000000; 6953 else 6954 enum flags = 0x40000000; 6955 6956 return ObjCBlock!(R, Params)(&_NSConcreteStackBlock, flags, &objcblock_invoke!(R, Params), dg); 6957 } 6958 6959 private struct Descriptor { 6960 c_ulong reserved; 6961 c_ulong size; 6962 const(char)* signature; 6963 } 6964 private extern(C) extern __gshared void*[32] _NSConcreteStackBlock; 6965 private __gshared auto objcblock_descriptor = Descriptor(0, ObjCBlock!().sizeof); 6966 private extern(C) R objcblock_invoke(R, Args...)(ObjCBlock!(R, Args)* block, Args args) { 6967 return block.dg(args); 6968 } 6969 6970 6971 /* End copy/paste chunk from Jacob Carlborg } */ 6972 6973 6974 /+ 6975 To let Cocoa know that you intend to use multiple threads, all you have to do is spawn a single thread using the NSThread class and let that thread immediately exit. Your thread entry point need not do anything. Just the act of spawning a thread using NSThread is enough to ensure that the locks needed by the Cocoa frameworks are put in place. 6976 6977 If you are not sure if Cocoa thinks your application is multithreaded or not, you can use the isMultiThreaded method of NSThread to check. 6978 +/ 6979 6980 6981 struct DeifiedNSString { 6982 char[16] sso; 6983 const(char)[] str; 6984 6985 this(NSString s) { 6986 auto len = s.length; 6987 if(len <= sso.length / 4) 6988 str = sso[]; 6989 else 6990 str = new char[](len * 4); 6991 6992 NSUInteger count; 6993 NSRange leftover; 6994 auto ret = s.getBytes(cast(char*) str.ptr, str.length, &count, NSStringEncoding.NSUTF8StringEncoding, NSStringEncodingConversionOptions.none, NSRange(0, len), &leftover); 6995 if(ret) 6996 str = str[0 .. count]; 6997 else 6998 throw new Exception("uh oh"); 6999 } 7000 } 7001 7002 extern (Objective-C) { 7003 import core.attribute; // : selector, optional; 7004 7005 alias NSUInteger = size_t; 7006 alias NSInteger = ptrdiff_t; 7007 alias unichar = wchar; 7008 struct SEL_; 7009 alias SEL_* SEL; 7010 // this is called plain `id` in objective C but i fear mistakes with that in D. like sure it is a type instead of a variable like most things called id but i still think it is weird. i might change my mind later. 7011 alias void* NSid; // FIXME? the docs say this is a pointer to an instance of a class, but that is not necessary a child of NSObject 7012 7013 extern class NSObject { 7014 static NSObject alloc() @selector("alloc"); 7015 NSObject init() @selector("init"); 7016 7017 void retain() @selector("retain"); 7018 void release() @selector("release"); 7019 void autorelease() @selector("autorelease"); 7020 7021 void performSelectorOnMainThread(SEL aSelector, NSid arg, bool waitUntilDone) @selector("performSelectorOnMainThread:withObject:waitUntilDone:"); 7022 } 7023 7024 // this is some kind of generic in objc... 7025 extern class NSArray : NSObject { 7026 static NSArray arrayWithObjects(NSid* objects, NSUInteger count) @selector("arrayWithObjects:count:"); 7027 } 7028 7029 extern class NSString : NSObject { 7030 override static NSString alloc() @selector("alloc"); 7031 override NSString init() @selector("init"); 7032 7033 NSString initWithUTF8String(const scope char* str) @selector("initWithUTF8String:"); 7034 7035 NSString initWithBytes( 7036 const(ubyte)* bytes, 7037 NSUInteger length, 7038 NSStringEncoding encoding 7039 ) @selector("initWithBytes:length:encoding:"); 7040 7041 unichar characterAtIndex(NSUInteger index) @selector("characterAtIndex:"); 7042 NSUInteger length() @selector("length"); 7043 const char* UTF8String() @selector("UTF8String"); 7044 7045 void getCharacters(wchar* buffer, NSRange range) @selector("getCharacters:range:"); 7046 7047 bool getBytes(void* buffer, NSUInteger maxBufferCount, NSUInteger* usedBufferCount, NSStringEncoding encoding, NSStringEncodingConversionOptions options, NSRange range, NSRange* leftover) @selector("getBytes:maxLength:usedLength:encoding:options:range:remainingRange:"); 7048 } 7049 7050 struct NSRange { 7051 NSUInteger loc; 7052 NSUInteger len; 7053 } 7054 7055 enum NSStringEncodingConversionOptions : NSInteger { 7056 none = 0, 7057 NSAllowLossyEncodingConversion = 1, 7058 NSExternalRepresentationEncodingConversion = 2 7059 } 7060 7061 enum NSEventType { 7062 idk 7063 7064 } 7065 7066 enum NSEventModifierFlags : NSUInteger { 7067 NSEventModifierFlagCapsLock = 1 << 16, 7068 NSEventModifierFlagShift = 1 << 17, 7069 NSEventModifierFlagControl = 1 << 18, 7070 NSEventModifierFlagOption = 1 << 19, // aka Alt 7071 NSEventModifierFlagCommand = 1 << 20, // aka super 7072 NSEventModifierFlagNumericPad = 1 << 21, 7073 NSEventModifierFlagHelp = 1 << 22, 7074 NSEventModifierFlagFunction = 1 << 23, 7075 NSEventModifierFlagDeviceIndependentFlagsMask = 0xffff0000UL 7076 } 7077 7078 extern class NSEvent : NSObject { 7079 NSEventType type() @selector("type"); 7080 7081 NSPoint locationInWindow() @selector("locationInWindow"); 7082 NSTimeInterval timestamp() @selector("timestamp"); 7083 NSWindow window() @selector("window"); // note: nullable 7084 NSEventModifierFlags modifierFlags() @selector("modifierFlags"); 7085 7086 NSString characters() @selector("characters"); 7087 NSString charactersIgnoringModifiers() @selector("charactersIgnoringModifiers"); 7088 ushort keyCode() @selector("keyCode"); 7089 ushort specialKey() @selector("specialKey"); 7090 7091 static NSUInteger pressedMouseButtons() @selector("pressedMouseButtons"); 7092 NSPoint locationInWindow() @selector("locationInWindow"); // in screen coordinates 7093 static NSPoint mouseLocation() @selector("mouseLocation"); // in screen coordinates 7094 NSInteger buttonNumber() @selector("buttonNumber"); 7095 7096 CGFloat deltaX() @selector("deltaX"); 7097 CGFloat deltaY() @selector("deltaY"); 7098 CGFloat deltaZ() @selector("deltaZ"); 7099 7100 bool hasPreciseScrollingDeltas() @selector("hasPreciseScrollingDeltas"); 7101 7102 CGFloat scrollingDeltaX() @selector("scrollingDeltaX"); 7103 CGFloat scrollingDeltaY() @selector("scrollingDeltaY"); 7104 7105 // @property(getter=isDirectionInvertedFromDevice, readonly) BOOL directionInvertedFromDevice; 7106 } 7107 7108 extern /* final */ class NSTimer : NSObject { // the docs say don't subclass this, but making it final breaks the bridge 7109 override static NSTimer alloc() @selector("alloc"); 7110 override NSTimer init() @selector("init"); 7111 7112 static NSTimer schedule(NSTimeInterval timeIntervalInSeconds, NSid target, SEL selector, NSid userInfo, bool repeats) @selector("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:"); 7113 7114 void fire() @selector("fire"); 7115 void invalidate() @selector("invalidate"); 7116 7117 bool valid() @selector("isValid"); 7118 // @property(copy) NSDate *fireDate; 7119 NSTimeInterval timeInterval() @selector("timeInterval"); 7120 NSid userInfo() @selector("userInfo"); 7121 7122 NSTimeInterval tolerance() @selector("tolerance"); 7123 NSTimeInterval tolerance(NSTimeInterval) @selector("setTolerance:"); 7124 } 7125 7126 alias NSTimeInterval = double; 7127 7128 extern class NSResponder : NSObject { 7129 NSMenu menu() @selector("menu"); 7130 void menu(NSMenu menu) @selector("setMenu:"); 7131 7132 void keyDown(NSEvent event) @selector("keyDown:"); 7133 void keyUp(NSEvent event) @selector("keyUp:"); 7134 7135 // - (void)interpretKeyEvents:(NSArray<NSEvent *> *)eventArray; 7136 7137 void mouseDown(NSEvent event) @selector("mouseDown:"); 7138 void mouseDragged(NSEvent event) @selector("mouseDragged:"); 7139 void mouseUp(NSEvent event) @selector("mouseUp:"); 7140 void mouseMoved(NSEvent event) @selector("mouseMoved:"); 7141 void mouseEntered(NSEvent event) @selector("mouseEntered:"); 7142 void mouseExited(NSEvent event) @selector("mouseExited:"); 7143 7144 void rightMouseDown(NSEvent event) @selector("rightMouseDown:"); 7145 void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:"); 7146 void rightMouseUp(NSEvent event) @selector("rightMouseUp:"); 7147 7148 void otherMouseDown(NSEvent event) @selector("otherMouseDown:"); 7149 void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:"); 7150 void otherMouseUp(NSEvent event) @selector("otherMouseUp:"); 7151 7152 void scrollWheel(NSEvent event) @selector("scrollWheel:"); 7153 7154 // touch events should also be here btw among others 7155 } 7156 7157 extern class NSApplication : NSResponder { 7158 static NSApplication shared_() @selector("sharedApplication"); 7159 7160 NSApplicationDelegate delegate_() @selector("delegate"); 7161 void delegate_(NSApplicationDelegate) @selector("setDelegate:"); 7162 7163 bool setActivationPolicy(NSApplicationActivationPolicy activationPolicy) @selector("setActivationPolicy:"); 7164 7165 void activateIgnoringOtherApps(bool flag) @selector("activateIgnoringOtherApps:"); 7166 7167 @property NSMenu mainMenu() @selector("mainMenu"); 7168 @property NSMenu mainMenu(NSMenu) @selector("setMainMenu:"); 7169 7170 void run() @selector("run"); 7171 7172 void terminate(void*) @selector("terminate:"); 7173 } 7174 7175 extern interface NSApplicationDelegate { 7176 void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:"); 7177 void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:"); 7178 bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:"); 7179 } 7180 7181 extern class NSNotification : NSObject { 7182 @property NSid object() @selector("object"); 7183 } 7184 7185 enum NSApplicationActivationPolicy : ptrdiff_t { 7186 /* The application is an ordinary app that appears in the Dock and may have a user interface. This is the default for bundled apps, unless overridden in the Info.plist. */ 7187 regular, 7188 7189 /* The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically or by clicking on one of its windows. This corresponds to LSUIElement=1 in the Info.plist. */ 7190 accessory, 7191 7192 /* The application does not appear in the Dock and may not create windows or be activated. This corresponds to LSBackgroundOnly=1 in the Info.plist. This is also the default for unbundled executables that do not have Info.plists. */ 7193 prohibited 7194 } 7195 7196 extern class NSGraphicsContext : NSObject { 7197 static NSGraphicsContext currentContext() @selector("currentContext"); 7198 NSGraphicsContext graphicsPort() @selector("graphicsPort"); 7199 } 7200 7201 extern class NSMenu : NSObject { 7202 override static NSMenu alloc() @selector("alloc"); 7203 7204 override NSMenu init() @selector("init"); 7205 NSMenu init(NSString title) @selector("initWithTitle:"); 7206 7207 void setSubmenu(NSMenu menu, NSMenuItem item) @selector("setSubmenu:forItem:"); 7208 void addItem(NSMenuItem newItem) @selector("addItem:"); 7209 7210 NSMenuItem addItem( 7211 NSString title, 7212 SEL selector, 7213 NSString charCode 7214 ) @selector("addItemWithTitle:action:keyEquivalent:"); 7215 } 7216 7217 extern class NSMenuItem : NSObject { 7218 override static NSMenuItem alloc() @selector("alloc"); 7219 override NSMenuItem init() @selector("init"); 7220 7221 NSMenuItem init( 7222 NSString title, 7223 SEL selector, 7224 NSString charCode 7225 ) @selector("initWithTitle:action:keyEquivalent:"); 7226 7227 void enabled(bool) @selector("setEnabled:"); 7228 7229 NSResponder target(NSResponder) @selector("setTarget:"); 7230 } 7231 7232 enum NSWindowStyleMask : size_t { 7233 borderless = 0, 7234 titled = 1 << 0, 7235 closable = 1 << 1, 7236 miniaturizable = 1 << 2, 7237 resizable = 1 << 3, 7238 7239 /* Specifies a window with textured background. Textured windows generally don't draw a top border line under the titlebar/toolbar. To get that line, use the NSUnifiedTitleAndToolbarWindowMask mask. 7240 */ 7241 texturedBackground = 1 << 8, 7242 7243 /* Specifies a window whose titlebar and toolbar have a unified look - that is, a continuous background. Under the titlebar and toolbar a horizontal separator line will appear. 7244 */ 7245 unifiedTitleAndToolbar = 1 << 12, 7246 7247 /* When set, the window will appear full screen. This mask is automatically toggled when toggleFullScreen: is called. 7248 */ 7249 fullScreen = 1 << 14, 7250 7251 /* If set, the contentView will consume the full size of the window; it can be combined with other window style masks, but is only respected for windows with a titlebar. 7252 Utilizing this mask opts-in to layer-backing. Utilize the contentLayoutRect or auto-layout contentLayoutGuide to layout views underneath the titlebar/toolbar area. 7253 */ 7254 fullSizeContentView = 1 << 15, 7255 7256 /* The following are only applicable for NSPanel (or a subclass thereof) 7257 */ 7258 utilityWindow = 1 << 4, 7259 docModalWindow = 1 << 6, 7260 nonactivatingPanel = 1 << 7, // Specifies that a panel that does not activate the owning application 7261 hUDWindow = 1 << 13 // Specifies a heads up display panel 7262 } 7263 7264 extern class NSWindow : NSObject { 7265 override static NSWindow alloc() @selector("alloc"); 7266 7267 override NSWindow init() @selector("init"); 7268 7269 NSWindow initWithContentRect( 7270 NSRect contentRect, 7271 NSWindowStyleMask style, 7272 NSBackingStoreType bufferingType, 7273 bool flag 7274 ) @selector("initWithContentRect:styleMask:backing:defer:"); 7275 7276 void makeKeyAndOrderFront(NSid sender) @selector("makeKeyAndOrderFront:"); 7277 NSView contentView() @selector("contentView"); 7278 void contentView(NSView view) @selector("setContentView:"); 7279 void orderFrontRegardless() @selector("orderFrontRegardless"); 7280 void center() @selector("center"); 7281 7282 NSRect frame() @selector("frame"); 7283 7284 NSRect contentRectForFrameRect(NSRect frameRect) @selector("contentRectForFrameRect:"); 7285 7286 NSString title() @selector("title"); 7287 void title(NSString value) @selector("setTitle:"); 7288 7289 void close() @selector("close"); 7290 7291 NSWindowDelegate delegate_() @selector("delegate"); 7292 void delegate_(NSWindowDelegate) @selector("setDelegate:"); 7293 7294 void setBackgroundColor(NSColor color) @selector("setBackgroundColor:"); 7295 } 7296 7297 extern interface NSWindowDelegate { 7298 @optional: 7299 void windowDidResize(NSNotification notification) @selector("windowDidResize:"); 7300 7301 NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:"); 7302 7303 void windowWillClose(NSNotification notification) @selector("windowWillClose:"); 7304 } 7305 7306 extern class NSView : NSResponder { 7307 override NSView init() @selector("init"); 7308 NSView initWithFrame(NSRect frameRect) @selector("initWithFrame:"); 7309 7310 void addSubview(NSView view) @selector("addSubview:"); 7311 7312 bool wantsLayer() @selector("wantsLayer"); 7313 void wantsLayer(bool value) @selector("setWantsLayer:"); 7314 7315 CALayer layer() @selector("layer"); 7316 void uiDelegate(NSObject) @selector("setUIDelegate:"); 7317 7318 void drawRect(NSRect rect) @selector("drawRect:"); 7319 bool isFlipped() @selector("isFlipped"); 7320 bool acceptsFirstResponder() @selector("acceptsFirstResponder"); 7321 bool setNeedsDisplay(bool) @selector("setNeedsDisplay:"); 7322 7323 // DO NOT USE: https://issues.dlang.org/show_bug.cgi?id=19017 7324 // an asm { pop RAX; } after getting the struct can kinda hack around this but still 7325 @property NSRect frame() @selector("frame"); 7326 @property NSRect frame(NSRect rect) @selector("setFrame:"); 7327 7328 void setFrameSize(NSSize newSize) @selector("setFrameSize:"); 7329 void setFrameOrigin(NSPoint newOrigin) @selector("setFrameOrigin:"); 7330 7331 void addSubview(NSView what) @selector("addSubview:"); 7332 void removeFromSuperview() @selector("removeFromSuperview"); 7333 } 7334 7335 extern class NSFont : NSObject { 7336 void set() @selector("set"); // sets it into the current graphics context 7337 void setInContext(NSGraphicsContext context) @selector("setInContext:"); 7338 7339 static NSFont fontWithName(NSString fontName, CGFloat fontSize) @selector("fontWithName:size:"); 7340 // fontWithDescriptor too 7341 // fontWithName and matrix too 7342 static NSFont systemFontOfSize(CGFloat fontSize) @selector("systemFontOfSize:"); 7343 // among others 7344 7345 @property CGFloat pointSize() @selector("pointSize"); 7346 @property bool isFixedPitch() @selector("isFixedPitch"); 7347 // fontDescriptor 7348 @property NSString displayName() @selector("displayName"); 7349 7350 @property CGFloat ascender() @selector("ascender"); 7351 @property CGFloat descender() @selector("descender"); // note it is negative 7352 @property CGFloat capHeight() @selector("capHeight"); 7353 @property CGFloat leading() @selector("leading"); 7354 @property CGFloat xHeight() @selector("xHeight"); 7355 // among many more 7356 } 7357 7358 extern class NSColor : NSObject { 7359 override static NSColor alloc() @selector("alloc"); 7360 static NSColor redColor() @selector("redColor"); 7361 static NSColor whiteColor() @selector("whiteColor"); 7362 7363 CGColorRef CGColor() @selector("CGColor"); 7364 } 7365 7366 extern class CALayer : NSObject { 7367 CGFloat borderWidth() @selector("borderWidth"); 7368 void borderWidth(CGFloat value) @selector("setBorderWidth:"); 7369 7370 CGColorRef borderColor() @selector("borderColor"); 7371 void borderColor(CGColorRef) @selector("setBorderColor:"); 7372 } 7373 7374 7375 extern class NSViewController : NSObject { 7376 NSView view() @selector("view"); 7377 void view(NSView view) @selector("setView:"); 7378 } 7379 7380 enum NSBackingStoreType : size_t { 7381 retained = 0, 7382 nonretained = 1, 7383 buffered = 2 7384 } 7385 7386 enum NSStringEncoding : NSUInteger { 7387 NSASCIIStringEncoding = 1, /* 0..127 only */ 7388 NSUTF8StringEncoding = 4, 7389 NSUnicodeStringEncoding = 10, 7390 7391 NSUTF16StringEncoding = NSUnicodeStringEncoding, 7392 NSUTF16BigEndianStringEncoding = 0x90000100, 7393 NSUTF16LittleEndianStringEncoding = 0x94000100, 7394 NSUTF32StringEncoding = 0x8c000100, 7395 NSUTF32BigEndianStringEncoding = 0x98000100, 7396 NSUTF32LittleEndianStringEncoding = 0x9c000100 7397 } 7398 7399 7400 struct CGColor; 7401 alias CGColorRef = CGColor*; 7402 7403 // note on the watch os it is float, not double 7404 alias CGFloat = double; 7405 7406 struct NSPoint { 7407 CGFloat x; 7408 CGFloat y; 7409 } 7410 7411 struct NSSize { 7412 CGFloat width; 7413 CGFloat height; 7414 } 7415 7416 struct NSRect { 7417 NSPoint origin; 7418 NSSize size; 7419 } 7420 7421 alias NSPoint CGPoint; 7422 alias NSSize CGSize; 7423 alias NSRect CGRect; 7424 7425 pragma(inline, true) NSPoint NSMakePoint(CGFloat x, CGFloat y) { 7426 NSPoint p; 7427 p.x = x; 7428 p.y = y; 7429 return p; 7430 } 7431 7432 pragma(inline, true) NSSize NSMakeSize(CGFloat w, CGFloat h) { 7433 NSSize s; 7434 s.width = w; 7435 s.height = h; 7436 return s; 7437 } 7438 7439 pragma(inline, true) NSRect NSMakeRect(CGFloat x, CGFloat y, CGFloat w, CGFloat h) { 7440 NSRect r; 7441 r.origin.x = x; 7442 r.origin.y = y; 7443 r.size.width = w; 7444 r.size.height = h; 7445 return r; 7446 } 7447 7448 7449 } 7450 7451 // helper raii refcount object 7452 static if(UseCocoa) 7453 struct MacString { 7454 union { 7455 // must be wrapped cuz of bug in dmd 7456 // referencing an init symbol when it should 7457 // just be null. but the union makes it work 7458 NSString s; 7459 } 7460 7461 // FIXME: if a string literal it would be kinda nice to use 7462 // the other function. but meh 7463 7464 this(scope const char[] str) { 7465 this.s = NSString.alloc.initWithBytes( 7466 cast(const(ubyte)*) str.ptr, 7467 str.length, 7468 NSStringEncoding.NSUTF8StringEncoding 7469 ); 7470 } 7471 7472 NSString borrow() { 7473 return s; 7474 } 7475 7476 this(this) { 7477 if(s !is null) 7478 s.retain(); 7479 } 7480 7481 ~this() { 7482 if(s !is null) { 7483 s.release(); 7484 s = null; 7485 } 7486 } 7487 } 7488 7489 extern(C) void NSLog(NSString, ...); 7490 extern(C) SEL sel_registerName(const(char)* str); 7491 7492 extern (Objective-C) __gshared NSApplication NSApp_; 7493 7494 NSApplication NSApp() { 7495 if(NSApp_ is null) 7496 NSApp_ = NSApplication.shared_; 7497 return NSApp_; 7498 } 7499 7500 // hacks to work around compiler bug 7501 extern(C) __gshared void* _D4arsd4core17NSGraphicsContext7__ClassZ = null; 7502 extern(C) __gshared void* _D4arsd4core6NSView7__ClassZ = null; 7503 extern(C) __gshared void* _D4arsd4core8NSWindow7__ClassZ = null; 7504 }