3
3
package runtime
4
4
5
5
import (
6
+ "math/bits"
7
+ "sync/atomic"
6
8
"unsafe"
7
9
)
8
10
@@ -12,6 +14,9 @@ func libc_write(fd int32, buf unsafe.Pointer, count uint) int
12
14
//export usleep
13
15
func usleep (usec uint ) int
14
16
17
+ //export pause
18
+ func pause () int32
19
+
15
20
// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
16
21
// Note: off_t is defined as int64 because:
17
22
// - musl (used on Linux) always defines it as int64
@@ -217,8 +222,47 @@ func nanosecondsToTicks(ns int64) timeUnit {
217
222
}
218
223
219
224
func sleepTicks (d timeUnit ) {
220
- // timeUnit is in nanoseconds, so need to convert to microseconds here.
221
- usleep (uint (d ) / 1000 )
225
+ // When there are no signal handlers present, we can simply go to sleep.
226
+ if ! hasSignals {
227
+ // timeUnit is in nanoseconds, so need to convert to microseconds here.
228
+ usleep (uint (d ) / 1000 )
229
+ return
230
+ }
231
+
232
+ if GOOS == "darwin" {
233
+ // Check for incoming signals.
234
+ if checkSignals () {
235
+ // Received a signal, so there's probably at least one goroutine
236
+ // that's runnable again.
237
+ return
238
+ }
239
+
240
+ // WARNING: there is a race condition here. If a signal arrives between
241
+ // checkSignals() and usleep(), the usleep() call will not exit early so
242
+ // the signal is delayed until usleep finishes or another signal
243
+ // arrives.
244
+ // There doesn't appear to be a simple way to fix this on MacOS.
245
+
246
+ // timeUnit is in nanoseconds, so need to convert to microseconds here.
247
+ result := usleep (uint (d ) / 1000 )
248
+ if result != 0 {
249
+ checkSignals ()
250
+ }
251
+ } else {
252
+ // Linux (and various other POSIX systems) implement sigtimedwait so we
253
+ // can do this in a non-racy way.
254
+ tinygo_wfi_mask (activeSignals )
255
+ if checkSignals () {
256
+ tinygo_wfi_unmask ()
257
+ return
258
+ }
259
+ signal := tinygo_wfi_sleep (activeSignals , uint64 (d ))
260
+ if signal >= 0 {
261
+ tinygo_signal_handler (signal )
262
+ checkSignals ()
263
+ }
264
+ tinygo_wfi_unmask ()
265
+ }
222
266
}
223
267
224
268
func getTime (clock int32 ) uint64 {
@@ -307,3 +351,183 @@ func growHeap() bool {
307
351
setHeapEnd (heapStart + heapSize )
308
352
return true
309
353
}
354
+
355
+ func init () {
356
+ // Set up a channel to receive signals into.
357
+ signalChan = make (chan uint32 , 1 )
358
+ }
359
+
360
+ var signalChan chan uint32
361
+
362
+ // Indicate whether signals have been registered.
363
+ var hasSignals bool
364
+
365
+ // Mask of signals that have been received. The signal handler atomically ORs
366
+ // signals into this value.
367
+ var receivedSignals uint32
368
+
369
+ var activeSignals uint32
370
+
371
+ //go:linkname signal_enable os/signal.signal_enable
372
+ func signal_enable (s uint32 ) {
373
+ if s >= 32 {
374
+ // TODO: to support higher signal numbers, we need to turn
375
+ // receivedSignals into a uint32 array.
376
+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
377
+ }
378
+ hasSignals = true
379
+ activeSignals |= 1 << s
380
+ // It's easier to implement this function in C.
381
+ tinygo_signal_enable (s )
382
+ }
383
+
384
+ //go:linkname signal_ignore os/signal.signal_ignore
385
+ func signal_ignore (s uint32 ) {
386
+ if s >= 32 {
387
+ // TODO: to support higher signal numbers, we need to turn
388
+ // receivedSignals into a uint32 array.
389
+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
390
+ }
391
+ activeSignals &^= 1 << s
392
+ tinygo_signal_ignore (s )
393
+ }
394
+
395
+ //go:linkname signal_disable os/signal.signal_disable
396
+ func signal_disable (s uint32 ) {
397
+ if s >= 32 {
398
+ // TODO: to support higher signal numbers, we need to turn
399
+ // receivedSignals into a uint32 array.
400
+ runtimePanicAt (returnAddress (0 ), "unsupported signal number" )
401
+ }
402
+ activeSignals &^= 1 << s
403
+ tinygo_signal_disable (s )
404
+ }
405
+
406
+ //go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle
407
+ func signal_waitUntilIdle () {
408
+ // Make sure all signals are sent on the channel.
409
+ for atomic .LoadUint32 (& receivedSignals ) != 0 {
410
+ checkSignals ()
411
+ Gosched ()
412
+ }
413
+
414
+ // Make sure all signals are processed.
415
+ for len (signalChan ) != 0 {
416
+ Gosched ()
417
+ }
418
+ }
419
+
420
+ //export tinygo_signal_enable
421
+ func tinygo_signal_enable (s uint32 )
422
+
423
+ //export tinygo_signal_ignore
424
+ func tinygo_signal_ignore (s uint32 )
425
+
426
+ //export tinygo_signal_disable
427
+ func tinygo_signal_disable (s uint32 )
428
+
429
+ // void tinygo_signal_handler(int sig);
430
+ //
431
+ //export tinygo_signal_handler
432
+ func tinygo_signal_handler (s int32 ) {
433
+ // This loop is essentially the atomic equivalent of the following:
434
+ //
435
+ // receivedSignals |= 1 << s
436
+ //
437
+ // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead of
438
+ // this loop.
439
+ for {
440
+ mask := uint32 (1 ) << uint32 (s )
441
+ val := atomic .LoadUint32 (& receivedSignals )
442
+ swapped := atomic .CompareAndSwapUint32 (& receivedSignals , val , val | mask )
443
+ if swapped {
444
+ break
445
+ }
446
+ }
447
+ }
448
+
449
+ //go:linkname signal_recv os/signal.signal_recv
450
+ func signal_recv () uint32 {
451
+ // Function called from os/signal to get the next received signal.
452
+ val := <- signalChan
453
+ checkSignals ()
454
+ return val
455
+ }
456
+
457
+ // Atomically find a signal that previously occured and send it into the
458
+ // signalChan channel. Return true if at least one signal was delivered this
459
+ // way, false otherwise.
460
+ func checkSignals () bool {
461
+ gotSignals := false
462
+ for {
463
+ // Extract the lowest numbered signal number from receivedSignals.
464
+ val := atomic .LoadUint32 (& receivedSignals )
465
+ if val == 0 {
466
+ // There is no signal ready to be received by the program (common
467
+ // case).
468
+ return gotSignals
469
+ }
470
+ num := uint32 (bits .TrailingZeros32 (val ))
471
+
472
+ // Do a non-blocking send on signalChan.
473
+ select {
474
+ case signalChan <- num :
475
+ // There was room free in the channel, so remove the signal number
476
+ // from the receivedSignals mask.
477
+ gotSignals = true
478
+ default :
479
+ // Could not send the signal number on the channel. This means
480
+ // there's still a signal pending. In that case, let it be received
481
+ // at which point checkSignals is called again to put the next one
482
+ // in the channel buffer.
483
+ return gotSignals
484
+ }
485
+
486
+ // Atomically clear the signal number from receivedSignals.
487
+ // TODO: use atomic.Uint32.Or once we drop support for Go 1.22 instead
488
+ // of this loop.
489
+ for {
490
+ newVal := val &^ (1 << num )
491
+ swapped := atomic .CompareAndSwapUint32 (& receivedSignals , val , newVal )
492
+ if swapped {
493
+ break
494
+ }
495
+ val = atomic .LoadUint32 (& receivedSignals )
496
+ }
497
+ }
498
+ }
499
+
500
+ //export tinygo_wfi_mask
501
+ func tinygo_wfi_mask (active uint32 )
502
+
503
+ //export tinygo_wfi_sleep
504
+ func tinygo_wfi_sleep (active uint32 , timeout uint64 ) int32
505
+
506
+ //export tinygo_wfi_wait
507
+ func tinygo_wfi_wait (active uint32 ) int32
508
+
509
+ //export tinygo_wfi_unmask
510
+ func tinygo_wfi_unmask ()
511
+
512
+ func waitForEvents () {
513
+ if hasSignals {
514
+ // We could have used pause() here, but that function is impossible to
515
+ // use in a race-free way:
516
+ // https://www.cipht.net/2023/11/30/perils-of-pause.html
517
+ // Therefore we need something better.
518
+ // Note: this is unsafe with multithreading, because sigprocmask is only
519
+ // defined for single-threaded applictions.
520
+ tinygo_wfi_mask (activeSignals )
521
+ if checkSignals () {
522
+ tinygo_wfi_unmask ()
523
+ return
524
+ }
525
+ signal := tinygo_wfi_wait (activeSignals )
526
+ tinygo_signal_handler (signal )
527
+ checkSignals ()
528
+ tinygo_wfi_unmask ()
529
+ } else {
530
+ // The program doesn't use signals, so this is a deadlock.
531
+ runtimePanic ("deadlocked: no event source" )
532
+ }
533
+ }
0 commit comments