v / vlib / context
Raw file | 178 loc (156 sloc) | 4.36 KB | Latest commit hash e81e0ac70
1// This module defines the Context type, which carries deadlines, cancellation signals,
2// and other request-scoped values across API boundaries and between processes.
3// Based on: https://github.com/golang/go/tree/master/src/context
4// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
5module context
6
7import rand
8import sync
9import time
10
11pub type CancelFn = fn ()
12
13pub interface Canceler {
14 id string
15mut:
16 cancel(remove_from_parent bool, err IError)
17 done() chan int
18}
19
20// A CancelContext can be canceled. When canceled, it also cancels any children
21// that implement Canceler.
22pub struct CancelContext {
23 id string
24mut:
25 context Context
26 mutex &sync.Mutex = sync.new_mutex()
27 done chan int
28 children map[string]Canceler
29 err IError = none
30}
31
32// with_cancel returns a copy of parent with a new done channel. The returned
33// context's done channel is closed when the returned cancel function is called
34// or when the parent context's done channel is closed, whichever happens first.
35//
36// Canceling this context releases resources associated with it, so code should
37// call cancel as soon as the operations running in this Context complete.
38pub fn with_cancel(mut parent Context) (Context, CancelFn) {
39 mut c := new_cancel_context(parent)
40 propagate_cancel(mut parent, mut c)
41 cancel_fn := fn [mut c] () {
42 c.cancel(true, canceled)
43 }
44 return Context(c), CancelFn(cancel_fn)
45}
46
47// new_cancel_context returns an initialized CancelContext.
48fn new_cancel_context(parent Context) &CancelContext {
49 return &CancelContext{
50 id: rand.uuid_v4()
51 context: parent
52 mutex: sync.new_mutex()
53 done: chan int{cap: 2}
54 err: none
55 }
56}
57
58pub fn (ctx &CancelContext) deadline() ?time.Time {
59 return none
60}
61
62pub fn (mut ctx CancelContext) done() chan int {
63 ctx.mutex.@lock()
64 done := ctx.done
65 ctx.mutex.unlock()
66 return done
67}
68
69pub fn (mut ctx CancelContext) err() IError {
70 ctx.mutex.@lock()
71 err := ctx.err
72 ctx.mutex.unlock()
73 return err
74}
75
76pub fn (ctx &CancelContext) value(key Key) ?Any {
77 if key == cancel_context_key {
78 return ctx
79 }
80 return ctx.context.value(key)
81}
82
83pub fn (ctx &CancelContext) str() string {
84 return context_name(ctx.context) + '.with_cancel'
85}
86
87fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) {
88 if err is none {
89 panic('context: internal error: missing cancel error')
90 }
91
92 ctx.mutex.@lock()
93 if ctx.err !is none {
94 ctx.mutex.unlock()
95 // already canceled
96 return
97 }
98
99 ctx.err = err
100
101 if !ctx.done.closed {
102 ctx.done <- 0
103 ctx.done.close()
104 }
105
106 for _, child in ctx.children {
107 // NOTE: acquiring the child's lock while holding parent's lock.
108 mut c := child
109 c.cancel(false, err)
110 }
111
112 ctx.children = map[string]Canceler{}
113 ctx.mutex.unlock()
114
115 if remove_from_parent {
116 mut cc := &ctx.context
117 remove_child(mut cc, ctx)
118 }
119}
120
121fn propagate_cancel(mut parent Context, mut child Canceler) {
122 done := parent.done()
123 select {
124 _ := <-done {
125 // parent is already canceled
126 child.cancel(false, parent.err())
127 return
128 }
129 }
130 mut p := parent_cancel_context(mut parent) or {
131 spawn fn (mut parent Context, mut child Canceler) {
132 pdone := parent.done()
133 select {
134 _ := <-pdone {
135 child.cancel(false, parent.err())
136 }
137 }
138 }(mut parent, mut child)
139 return
140 }
141
142 if p.err is none {
143 p.children[child.id] = child
144 } else {
145 // parent has already been canceled
146 child.cancel(false, p.err)
147 }
148}
149
150// parent_cancel_context returns the underlying CancelContext for parent.
151// It does this by looking up parent.value(&cancel_context_key) to find
152// the innermost enclosing CancelContext and then checking whether
153// parent.done() matches that CancelContext. (If not, the CancelContext
154// has been wrapped in a custom implementation providing a
155// different done channel, in which case we should not bypass it.)
156fn parent_cancel_context(mut parent Context) ?&CancelContext {
157 done := parent.done()
158 if done.closed {
159 return none
160 }
161 mut p := parent.value(cancel_context_key)?
162 match mut p {
163 CancelContext {
164 pdone := p.done()
165 if done == pdone {
166 return p
167 }
168 }
169 else {}
170 }
171 return none
172}
173
174// remove_child removes a context from its parent.
175fn remove_child(mut parent Context, child Canceler) {
176 mut p := parent_cancel_context(mut parent) or { return }
177 p.children.delete(child.id)
178}