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

View as plain text