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  	mutexMLocksDelta = 16
    29  )
    30  
    31  type mWaitList struct{}
    32  
    33  func lockVerifyMSize() {}
    34  
    35  func mutexContended(l *mutex) bool {
    36  	return false
    37  }
    38  
    39  func lock(l *mutex) {
    40  	lockWithRank(l, getLockRank(l))
    41  }
    42  
    43  func lock2(l *mutex) {
    44  	if l.key == mutex_locked {
    45  		// js/wasm is single-threaded so we should never
    46  		// observe this.
    47  		throw("self deadlock")
    48  	}
    49  	gp := getg()
    50  	if gp.m.locks < 0 {
    51  		throw("lock count")
    52  	}
    53  	gp.m.locks += mutexMLocksDelta
    54  	l.key = mutex_locked
    55  }
    56  
    57  func unlock(l *mutex) {
    58  	unlockWithRank(l)
    59  }
    60  
    61  func unlock2(l *mutex) {
    62  	if l.key == mutex_unlocked {
    63  		throw("unlock of unlocked lock")
    64  	}
    65  	gp := getg()
    66  	gp.m.locks -= mutexMLocksDelta
    67  	if gp.m.locks < 0 {
    68  		throw("lock count")
    69  	}
    70  	l.key = mutex_unlocked
    71  }
    72  
    73  // One-time notifications.
    74  
    75  // Linked list of notes with a deadline.
    76  var allDeadlineNotes *note
    77  
    78  func noteclear(n *note) {
    79  	n.status = note_cleared
    80  }
    81  
    82  func notewakeup(n *note) {
    83  	if n.status == note_woken {
    84  		throw("notewakeup - double wakeup")
    85  	}
    86  	cleared := n.status == note_cleared
    87  	n.status = note_woken
    88  	if cleared {
    89  		goready(n.gp, 1)
    90  	}
    91  }
    92  
    93  func notesleep(n *note) {
    94  	throw("notesleep not supported by js")
    95  }
    96  
    97  func notetsleep(n *note, ns int64) bool {
    98  	throw("notetsleep not supported by js")
    99  	return false
   100  }
   101  
   102  // same as runtimeĀ·notetsleep, but called on user g (not g0)
   103  func notetsleepg(n *note, ns int64) bool {
   104  	gp := getg()
   105  	if gp == gp.m.g0 {
   106  		throw("notetsleepg on g0")
   107  	}
   108  
   109  	if ns >= 0 {
   110  		deadline := nanotime() + ns
   111  		delay := ns/1000000 + 1 // round up
   112  		if delay > 1<<31-1 {
   113  			delay = 1<<31 - 1 // cap to max int32
   114  		}
   115  
   116  		id := scheduleTimeoutEvent(delay)
   117  
   118  		n.gp = gp
   119  		n.deadline = deadline
   120  		if allDeadlineNotes != nil {
   121  			allDeadlineNotes.allprev = n
   122  		}
   123  		n.allnext = allDeadlineNotes
   124  		allDeadlineNotes = n
   125  
   126  		gopark(nil, nil, waitReasonSleep, traceBlockSleep, 1)
   127  
   128  		clearTimeoutEvent(id) // note might have woken early, clear timeout
   129  
   130  		n.gp = nil
   131  		n.deadline = 0
   132  		if n.allprev != nil {
   133  			n.allprev.allnext = n.allnext
   134  		}
   135  		if allDeadlineNotes == n {
   136  			allDeadlineNotes = n.allnext
   137  		}
   138  		n.allprev = nil
   139  		n.allnext = nil
   140  
   141  		return n.status == note_woken
   142  	}
   143  
   144  	for n.status != note_woken {
   145  		n.gp = gp
   146  
   147  		gopark(nil, nil, waitReasonZero, traceBlockGeneric, 1)
   148  
   149  		n.gp = nil
   150  	}
   151  	return true
   152  }
   153  
   154  // checkTimeouts resumes goroutines that are waiting on a note which has reached its deadline.
   155  func checkTimeouts() {
   156  	now := nanotime()
   157  	for n := allDeadlineNotes; n != nil; n = n.allnext {
   158  		if n.status == note_cleared && n.deadline != 0 && now >= n.deadline {
   159  			n.status = note_timeout
   160  			goready(n.gp, 1)
   161  		}
   162  	}
   163  }
   164  
   165  // events is a stack of calls from JavaScript into Go.
   166  var events []*event
   167  
   168  type event struct {
   169  	// g was the active goroutine when the call from JavaScript occurred.
   170  	// It needs to be active when returning to JavaScript.
   171  	gp *g
   172  	// returned reports whether the event handler has returned.
   173  	// When all goroutines are idle and the event handler has returned,
   174  	// then g gets resumed and returns the execution to JavaScript.
   175  	returned bool
   176  }
   177  
   178  type timeoutEvent struct {
   179  	id int32
   180  	// The time when this timeout will be triggered.
   181  	time int64
   182  }
   183  
   184  // diff calculates the difference of the event's trigger time and x.
   185  func (e *timeoutEvent) diff(x int64) int64 {
   186  	if e == nil {
   187  		return 0
   188  	}
   189  
   190  	diff := x - idleTimeout.time
   191  	if diff < 0 {
   192  		diff = -diff
   193  	}
   194  	return diff
   195  }
   196  
   197  // clear cancels this timeout event.
   198  func (e *timeoutEvent) clear() {
   199  	if e == nil {
   200  		return
   201  	}
   202  
   203  	clearTimeoutEvent(e.id)
   204  }
   205  
   206  // The timeout event started by beforeIdle.
   207  var idleTimeout *timeoutEvent
   208  
   209  // beforeIdle gets called by the scheduler if no goroutine is awake.
   210  // If we are not already handling an event, then we pause for an async event.
   211  // If an event handler returned, we resume it and it will pause the execution.
   212  // beforeIdle either returns the specific goroutine to schedule next or
   213  // indicates with otherReady that some goroutine became ready.
   214  // TODO(drchase): need to understand if write barriers are really okay in this context.
   215  //
   216  //go:yeswritebarrierrec
   217  func beforeIdle(now, pollUntil int64) (gp *g, otherReady bool) {
   218  	delay := int64(-1)
   219  	if pollUntil != 0 {
   220  		// round up to prevent setTimeout being called early
   221  		delay = (pollUntil-now-1)/1e6 + 1
   222  		if delay > 1e9 {
   223  			// An arbitrary cap on how long to wait for a timer.
   224  			// 1e9 ms == ~11.5 days.
   225  			delay = 1e9
   226  		}
   227  	}
   228  
   229  	if delay > 0 && (idleTimeout == nil || idleTimeout.diff(pollUntil) > 1e6) {
   230  		// If the difference is larger than 1 ms, we should reschedule the timeout.
   231  		idleTimeout.clear()
   232  
   233  		idleTimeout = &timeoutEvent{
   234  			id:   scheduleTimeoutEvent(delay),
   235  			time: pollUntil,
   236  		}
   237  	}
   238  
   239  	if len(events) == 0 {
   240  		// TODO: this is the line that requires the yeswritebarrierrec
   241  		go handleAsyncEvent()
   242  		return nil, true
   243  	}
   244  
   245  	e := events[len(events)-1]
   246  	if e.returned {
   247  		return e.gp, false
   248  	}
   249  	return nil, false
   250  }
   251  
   252  var idleStart int64
   253  
   254  func handleAsyncEvent() {
   255  	idleStart = nanotime()
   256  	pause(sys.GetCallerSP() - 16)
   257  }
   258  
   259  // clearIdleTimeout clears our record of the timeout started by beforeIdle.
   260  func clearIdleTimeout() {
   261  	idleTimeout.clear()
   262  	idleTimeout = nil
   263  }
   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(sys.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