github.com/x04/go/src@v0.0.0-20200202162449-3d481ceb3525/runtime/lock_js.go (about)

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