Source file src/runtime/lock_js.go

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

View as plain text