github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/lock_js.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build js && wasm
     6  
     7  package runtime
     8  
     9  import _ "unsafe" // for go:linkname
    10  
    11  // js/wasm has no support for threads yet. There is no preemption.
    12  
    13  const (
    14  	mutex_unlocked = 0
    15  	mutex_locked   = 1
    16  
    17  	note_cleared = 0
    18  	note_woken   = 1
    19  	note_timeout = 2
    20  
    21  	active_spin     = 4
    22  	active_spin_cnt = 30
    23  	passive_spin    = 1
    24  )
    25  
    26  func lock(l *mutex) {
    27  	lockWithRank(l, getLockRank(l))
    28  }
    29  
    30  func lock2(l *mutex) {
    31  	if l.key == mutex_locked {
    32  		// js/wasm is single-threaded so we should never
    33  		// observe this.
    34  		throw("self deadlock")
    35  	}
    36  	gp := getg()
    37  	if gp.m.locks < 0 {
    38  		throw("lock count")
    39  	}
    40  	gp.m.locks++
    41  	l.key = mutex_locked
    42  }
    43  
    44  func unlock(l *mutex) {
    45  	unlockWithRank(l)
    46  }
    47  
    48  func unlock2(l *mutex) {
    49  	if l.key == mutex_unlocked {
    50  		throw("unlock of unlocked lock")
    51  	}
    52  	gp := getg()
    53  	gp.m.locks--
    54  	if gp.m.locks < 0 {
    55  		throw("lock count")
    56  	}
    57  	l.key = mutex_unlocked
    58  }
    59  
    60  // One-time notifications.
    61  
    62  type noteWithTimeout struct {
    63  	gp       *g
    64  	deadline int64
    65  }
    66  
    67  var (
    68  	notes            = make(map[*note]*g)
    69  	notesWithTimeout = make(map[*note]noteWithTimeout)
    70  )
    71  
    72  func noteclear(n *note) {
    73  	n.key = note_cleared
    74  }
    75  
    76  func notewakeup(n *note) {
    77  	// gp := getg()
    78  	if n.key == note_woken {
    79  		throw("notewakeup - double wakeup")
    80  	}
    81  	cleared := n.key == note_cleared
    82  	n.key = note_woken
    83  	if cleared {
    84  		goready(notes[n], 1)
    85  	}
    86  }
    87  
    88  func notesleep(n *note) {
    89  	throw("notesleep not supported by js")
    90  }
    91  
    92  func notetsleep(n *note, ns int64) bool {
    93  	throw("notetsleep not supported by js")
    94  	return false
    95  }
    96  
    97  // same as runtimeĀ·notetsleep, but called on user g (not g0)
    98  func notetsleepg(n *note, ns int64) bool {
    99  	gp := getg()
   100  	if gp == gp.m.g0 {
   101  		throw("notetsleepg on g0")
   102  	}
   103  
   104  	if ns >= 0 {
   105  		deadline := nanotime() + ns
   106  		delay := ns/1000000 + 1 // round up
   107  		if delay > 1<<31-1 {
   108  			delay = 1<<31 - 1 // cap to max int32
   109  		}
   110  
   111  		id := scheduleTimeoutEvent(delay)
   112  		mp := acquirem()
   113  		notes[n] = gp
   114  		notesWithTimeout[n] = noteWithTimeout{gp: gp, deadline: deadline}
   115  		releasem(mp)
   116  
   117  		gopark(nil, nil, waitReasonSleep, traceEvNone, 1)
   118  
   119  		clearTimeoutEvent(id) // note might have woken early, clear timeout
   120  		clearIdleID()
   121  
   122  		mp = acquirem()
   123  		delete(notes, n)
   124  		delete(notesWithTimeout, n)
   125  		releasem(mp)
   126  
   127  		return n.key == note_woken
   128  	}
   129  
   130  	for n.key != note_woken {
   131  		mp := acquirem()
   132  		notes[n] = gp
   133  		releasem(mp)
   134  
   135  		gopark(nil, nil, waitReasonZero, traceEvNone, 1)
   136  
   137  		mp = acquirem()
   138  		delete(notes, n)
   139  		releasem(mp)
   140  	}
   141  	return true
   142  }
   143  
   144  // checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline.
   145  // TODO(drchase): need to understand if write barriers are really okay in this context.
   146  //
   147  //go:yeswritebarrierrec
   148  func checkTimeouts() {
   149  	now := nanotime()
   150  	// TODO: map iteration has the write barriers in it; is that okay?
   151  	for n, nt := range notesWithTimeout {
   152  		if n.key == note_cleared && now >= nt.deadline {
   153  			n.key = note_timeout
   154  			goready(nt.gp, 1)
   155  		}
   156  	}
   157  }
   158  
   159  // events is a stack of calls from JavaScript into Go.
   160  var events []*event
   161  
   162  type event struct {
   163  	// g was the active goroutine when the call from JavaScript occurred.
   164  	// It needs to be active when returning to JavaScript.
   165  	gp *g
   166  	// returned reports whether the event handler has returned.
   167  	// When all goroutines are idle and the event handler has returned,
   168  	// then g gets resumed and returns the execution to JavaScript.
   169  	returned bool
   170  }
   171  
   172  // The timeout event started by beforeIdle.
   173  var idleID int32
   174  
   175  // beforeIdle gets called by the scheduler if no goroutine is awake.
   176  // If we are not already handling an event, then we pause for an async event.
   177  // If an event handler returned, we resume it and it will pause the execution.
   178  // beforeIdle either returns the specific goroutine to schedule next or
   179  // indicates with otherReady that some goroutine became ready.
   180  // TODO(drchase): need to understand if write barriers are really okay in this context.
   181  //
   182  //go:yeswritebarrierrec
   183  func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) {
   184  	delay := int64(-1)
   185  	if pollUntil != 0 {
   186  		delay = pollUntil - now
   187  	}
   188  
   189  	if delay > 0 {
   190  		clearIdleID()
   191  		if delay < 1e6 {
   192  			delay = 1
   193  		} else if delay < 1e15 {
   194  			delay = delay / 1e6
   195  		} else {
   196  			// An arbitrary cap on how long to wait for a timer.
   197  			// 1e9 ms == ~11.5 days.
   198  			delay = 1e9
   199  		}
   200  		idleID = scheduleTimeoutEvent(delay)
   201  	}
   202  
   203  	if len(events) == 0 {
   204  		// TODO: this is the line that requires the yeswritebarrierrec
   205  		go handleAsyncEvent()
   206  		return nil, true
   207  	}
   208  
   209  	e := events[len(events)-1]
   210  	if e.returned {
   211  		return e.gp, false
   212  	}
   213  	return nil, false
   214  }
   215  
   216  func handleAsyncEvent() {
   217  	pause(getcallersp() - 16)
   218  }
   219  
   220  // clearIdleID clears our record of the timeout started by beforeIdle.
   221  func clearIdleID() {
   222  	if idleID != 0 {
   223  		clearTimeoutEvent(idleID)
   224  		idleID = 0
   225  	}
   226  }
   227  
   228  // pause sets SP to newsp and pauses the execution of Go's WebAssembly code until an event is triggered.
   229  func pause(newsp uintptr)
   230  
   231  // scheduleTimeoutEvent tells the WebAssembly environment to trigger an event after ms milliseconds.
   232  // It returns a timer id that can be used with clearTimeoutEvent.
   233  //
   234  //go:wasmimport gojs runtime.scheduleTimeoutEvent
   235  func scheduleTimeoutEvent(ms int64) int32
   236  
   237  // clearTimeoutEvent clears a timeout event scheduled by scheduleTimeoutEvent.
   238  //
   239  //go:wasmimport gojs runtime.clearTimeoutEvent
   240  func clearTimeoutEvent(id int32)
   241  
   242  // handleEvent gets invoked on a call from JavaScript into Go. It calls the event handler of the syscall/js package
   243  // and then parks the handler goroutine to allow other goroutines to run before giving execution back to JavaScript.
   244  // When no other goroutine is awake any more, beforeIdle resumes the handler goroutine. Now that the same goroutine
   245  // is running as was running when the call came in from JavaScript, execution can be safely passed back to JavaScript.
   246  func handleEvent() {
   247  	e := &event{
   248  		gp:       getg(),
   249  		returned: false,
   250  	}
   251  	events = append(events, e)
   252  
   253  	eventHandler()
   254  
   255  	clearIdleID()
   256  
   257  	// wait until all goroutines are idle
   258  	e.returned = true
   259  	gopark(nil, nil, waitReasonZero, traceEvNone, 1)
   260  
   261  	events[len(events)-1] = nil
   262  	events = events[:len(events)-1]
   263  
   264  	// return execution to JavaScript
   265  	pause(getcallersp() - 16)
   266  }
   267  
   268  var eventHandler func()
   269  
   270  //go:linkname setEventHandler syscall/js.setEventHandler
   271  func setEventHandler(fn func()) {
   272  	eventHandler = fn
   273  }