aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/context/cancel.v
blob: 1a0ae5701231209d78cd56ec277b20e986172c47 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// This module defines the Context type, which carries deadlines, cancellation signals,
// and other request-scoped values across API boundaries and between processes.
// Based off:   https://github.com/golang/go/tree/master/src/context
// Last commit: https://github.com/golang/go/commit/52bf14e0e8bdcd73f1ddfb0c4a1d0200097d3ba2
module context

import rand
import sync
import time

pub interface Canceler {
	id string
	cancel(remove_from_parent bool, err IError)
	done() chan int
}

pub fn cancel(ctx Context) {
	match mut ctx {
		CancelContext {
			ctx.cancel(true, canceled)
		}
		TimerContext {
			ctx.cancel(true, canceled)
		}
		else {}
	}
}

// A CancelContext can be canceled. When canceled, it also cancels any children
// that implement Canceler.
pub struct CancelContext {
	id string
mut:
	context  Context
	mutex    &sync.Mutex
	done     chan int
	children map[string]Canceler
	err      IError
}

// with_cancel returns a copy of parent with a new done channel. The returned
// context's done channel is closed when the returned cancel function is called
// or when the parent context's done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
pub fn with_cancel(parent Context) Context {
	mut c := new_cancel_context(parent)
	propagate_cancel(parent, mut c)
	return Context(c)
}

// new_cancel_context returns an initialized CancelContext.
fn new_cancel_context(parent Context) &CancelContext {
	return &CancelContext{
		id: rand.uuid_v4()
		context: parent
		mutex: sync.new_mutex()
		done: chan int{cap: 2}
		err: none
	}
}

pub fn (ctx CancelContext) deadline() ?time.Time {
	return none
}

pub fn (mut ctx CancelContext) done() chan int {
	ctx.mutex.@lock()
	done := ctx.done
	ctx.mutex.unlock()
	return done
}

pub fn (mut ctx CancelContext) err() IError {
	ctx.mutex.@lock()
	err := ctx.err
	ctx.mutex.unlock()
	return err
}

pub fn (ctx CancelContext) value(key string) ?voidptr {
	if key == cancel_context_key {
		return voidptr(unsafe { &ctx })
	}
	return ctx.context.value(key)
}

pub fn (ctx CancelContext) str() string {
	return context_name(ctx.context) + '.with_cancel'
}

fn (mut ctx CancelContext) cancel(remove_from_parent bool, err IError) {
	if err is none {
		panic('context: internal error: missing cancel error')
	}

	ctx.mutex.@lock()
	if ctx.err !is none {
		ctx.mutex.unlock()
		// already canceled
		return
	}

	ctx.err = err

	if !ctx.done.closed {
		ctx.done <- 0
		ctx.done.close()
	}

	for _, child in ctx.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err)
	}

	ctx.children = map[string]Canceler{}
	ctx.mutex.unlock()

	if remove_from_parent {
		remove_child(ctx.context, ctx)
	}
}

fn propagate_cancel(parent Context, mut child Canceler) {
	done := parent.done()
	select {
		_ := <-done {
			// parent is already canceled
			child.cancel(false, parent.err())
			return
		}
	}
	mut p := parent_cancel_context(parent) or {
		go fn (parent Context, mut child Canceler) {
			pdone := parent.done()
			select {
				_ := <-pdone {
					child.cancel(false, parent.err())
				}
			}
		}(parent, mut child)
		return
	}

	if p.err is none {
		p.children[child.id] = *child
	} else {
		// parent has already been canceled
		child.cancel(false, p.err)
	}
}

// parent_cancel_context returns the underlying CancelContext for parent.
// It does this by looking up parent.value(&cancel_context_key) to find
// the innermost enclosing CancelContext and then checking whether
// parent.done() matches that CancelContext. (If not, the CancelContext
// has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.)
fn parent_cancel_context(parent Context) ?CancelContext {
	done := parent.done()
	if done.closed {
		return none
	}
	if p_ptr := parent.value(cancel_context_key) {
		if !isnil(p_ptr) {
			mut p := &CancelContext(p_ptr)
			pdone := p.done()
			if done == pdone {
				return *p
			}
		}
	}
	return none
}

// remove_child removes a context from its parent.
fn remove_child(parent Context, child Canceler) {
	mut p := parent_cancel_context(parent) or { return }
	p.children.delete(child.id)
}