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

View as plain text