144 lines
3.4 KiB
V
144 lines
3.4 KiB
V
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
//
|
|
// Goroutine parking (blocking/unblocking) mechanism.
|
|
// Translated from Go's gopark/goready in proc.go.
|
|
//
|
|
// When a goroutine needs to block (e.g., waiting on a channel),
|
|
// it "parks" itself - yielding its M to the scheduler.
|
|
// When the condition is met, another goroutine "readies" it
|
|
// by putting it back on a run queue.
|
|
module goroutines
|
|
|
|
// gopark puts the current goroutine into a waiting state.
|
|
// The goroutine can be made runnable again by calling goready.
|
|
// reason describes why the goroutine is parking (for debugging).
|
|
// Translated from Go's gopark() in proc.go.
|
|
pub fn gopark(reason string) {
|
|
mut mp := get_current_m()
|
|
if mp == unsafe { nil } {
|
|
return
|
|
}
|
|
mut gp := mp.curg
|
|
if gp == unsafe { nil } {
|
|
return
|
|
}
|
|
|
|
gp.status = .waiting
|
|
gp.wait_reason = reason
|
|
|
|
// Dissociate G from M
|
|
mp.curg = unsafe { nil }
|
|
gp.m = unsafe { nil }
|
|
|
|
// Switch back to the scheduler (M's g0 context)
|
|
context_switch(mut &gp.context, &mp.g0.context)
|
|
}
|
|
|
|
// goready puts a waiting goroutine back on a run queue.
|
|
// Translated from Go's goready() in proc.go.
|
|
pub fn goready(gp &Goroutine) {
|
|
if gp == unsafe { nil } {
|
|
return
|
|
}
|
|
mut g := unsafe { gp }
|
|
if g.status != .waiting {
|
|
return
|
|
}
|
|
g.status = .runnable
|
|
g.wait_reason = ''
|
|
|
|
// Put it on the current P's local run queue, or global if no P
|
|
mut pp := get_current_p()
|
|
if pp != unsafe { nil } {
|
|
runq_put(mut pp, g, true)
|
|
} else {
|
|
glob_runq_put(g)
|
|
}
|
|
|
|
// Try to wake an idle P to run this goroutine
|
|
wake_p()
|
|
}
|
|
|
|
// gosched yields the processor, allowing other goroutines to run.
|
|
// Translated from Go's Gosched() / goschedImpl() in proc.go.
|
|
pub fn gosched() {
|
|
mut mp := get_current_m()
|
|
if mp == unsafe { nil } {
|
|
return
|
|
}
|
|
mut gp := mp.curg
|
|
if gp == unsafe { nil } {
|
|
return
|
|
}
|
|
mut pp := mp.p
|
|
if pp == unsafe { nil } {
|
|
return
|
|
}
|
|
|
|
// Put the current G back on the run queue as runnable
|
|
gp.status = .runnable
|
|
mp.curg = unsafe { nil }
|
|
gp.m = unsafe { nil }
|
|
|
|
// Put on local queue
|
|
runq_put(mut pp, gp, false)
|
|
|
|
// Switch back to scheduler
|
|
context_switch(mut &gp.context, &mp.g0.context)
|
|
}
|
|
|
|
// Sudog represents a G in a wait list (e.g., channel wait queue).
|
|
// Translated from Go's sudog struct in runtime2.go.
|
|
pub struct Sudog {
|
|
pub mut:
|
|
g &Goroutine = unsafe { nil } // the waiting goroutine
|
|
next &Sudog = unsafe { nil } // next in wait list
|
|
prev &Sudog = unsafe { nil } // prev in wait list
|
|
elem voidptr // data element (may point to stack)
|
|
success bool // true if woken by successful channel op
|
|
c voidptr // channel pointer
|
|
}
|
|
|
|
// WaitQ is a wait queue of Sudogs (used by channels).
|
|
// Translated from Go's waitq struct in runtime2.go.
|
|
pub struct WaitQ {
|
|
pub mut:
|
|
first &Sudog = unsafe { nil }
|
|
last &Sudog = unsafe { nil }
|
|
}
|
|
|
|
pub fn (mut q WaitQ) enqueue(s &Sudog) {
|
|
mut sg := unsafe { s }
|
|
sg.next = unsafe { nil }
|
|
sg.prev = q.last
|
|
if q.last != unsafe { nil } {
|
|
q.last.next = sg
|
|
} else {
|
|
q.first = sg
|
|
}
|
|
q.last = sg
|
|
}
|
|
|
|
pub fn (mut q WaitQ) dequeue() &Sudog {
|
|
sg := q.first
|
|
if sg == unsafe { nil } {
|
|
return unsafe { nil }
|
|
}
|
|
q.first = sg.next
|
|
if q.first != unsafe { nil } {
|
|
q.first.prev = unsafe { nil }
|
|
} else {
|
|
q.last = unsafe { nil }
|
|
}
|
|
mut s := unsafe { sg }
|
|
s.next = unsafe { nil }
|
|
s.prev = unsafe { nil }
|
|
return s
|
|
}
|
|
|
|
pub fn (q &WaitQ) empty() bool {
|
|
return q.first == unsafe { nil }
|
|
}
|