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

View as plain text