Black Lives Matter. Support the Equal Justice Initiative.

Source file src/runtime/lock_js.go

Documentation: runtime

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

View as plain text