v2 / vlib / v / tests / concurrency / shared_nested_lock_runtime_test.v
142 lines · 137 sloc · 3.39 KB · 2f8a6cf03f4e1c519a20ee3aa120c158c98db0c3
Raw
1import os
2import time
3
4const vexe = @VEXE
5const source_run_poll_interval = 50 * time.millisecond
6const source_run_timeout_ticks = 600
7
8struct SourceRunResult {
9 code int
10 output string
11}
12
13fn run_source_with_timeout(source_name string, source string) !SourceRunResult {
14 base_dir := os.join_path(os.vtmp_dir(), '${source_name}_${os.getpid()}')
15 source_path := os.join_path(base_dir, '${source_name}.v')
16 os.rmdir_all(base_dir) or {}
17 os.mkdir_all(base_dir)!
18 defer {
19 os.rmdir_all(base_dir) or {}
20 }
21 os.write_file(source_path, source)!
22 mut p := os.new_process(vexe)
23 p.set_args(['run', source_path])
24 p.set_redirect_stdio()
25 // `v run` forks a grandchild for the compiled binary. On timeout, killing only the
26 // child V leaves that grandchild alive holding the stdout/stderr pipe ends, which
27 // makes stdout_slurp() block forever waiting for EOF (notably on musl, where the
28 // nested rwlock simply hangs instead of returning EDEADLK). Kill the whole pgroup.
29 p.use_pgroup = true
30 p.run()
31 mut timeout_ticks := 0
32 for p.is_alive() && timeout_ticks < source_run_timeout_ticks {
33 time.sleep(source_run_poll_interval)
34 timeout_ticks++
35 }
36 if p.is_alive() {
37 p.signal_pgkill()
38 p.wait()
39 stdout := p.stdout_slurp()
40 stderr := p.stderr_slurp()
41 p.close()
42 return error('timed out\nstdout:\n${stdout}\nstderr:\n${stderr}')
43 }
44 p.wait()
45 stdout := p.stdout_slurp()
46 stderr := p.stderr_slurp()
47 code := p.code
48 p.close()
49 return SourceRunResult{
50 code: code
51 output: stdout + stderr
52 }
53}
54
55fn test_nested_shared_lock_fails_without_hanging() {
56 $if windows {
57 return
58 }
59 source := [
60 'module main',
61 '',
62 'fn main() {',
63 '\tshared connections := []int{}',
64 '\tlock_function(shared connections)',
65 '\tlock connections {',
66 "\t\tprintln('unreachable')",
67 '\t}',
68 '}',
69 '',
70 'fn lock_function(shared connections []int) {',
71 '\tnested_lock_function := fn [shared connections] () {',
72 '\t\tlock connections {',
73 "\t\t\tprintln('unreachable nested')",
74 '\t\t}',
75 '\t}',
76 '\tlock connections {',
77 '\t\tnested_lock_function()',
78 '\t}',
79 '}',
80 ].join_lines()
81 result := run_source_with_timeout('shared_nested_lock_runtime', source) or {
82 assert false, 'nested shared lock hung\n${err}'
83 return
84 }
85 assert result.code != 0, 'expected nested shared lock failure, got output:\n${result.output}'
86 assert result.output.contains('Resource deadlock avoided'), result.output
87}
88
89fn test_shared_generic_heap_method_call_does_not_hang() {
90 source := [
91 'module main',
92 '',
93 '@[heap]',
94 'struct Topic[T] {',
95 'mut:',
96 '\tlist List[T]',
97 '}',
98 '',
99 '@[heap]',
100 'struct List[T] {',
101 'mut:',
102 '\tnode ?&Node[T]',
103 '}',
104 '',
105 '@[heap]',
106 'struct Node[T] {',
107 'mut:',
108 '\tdata T',
109 '\tnext ?&Node[T]',
110 '}',
111 '',
112 'fn Topic.new[T]() Topic[T] {',
113 '\treturn Topic[T]{}',
114 '}',
115 '',
116 'fn (mut l List[T]) push(val T) {',
117 '\tmut node := Node[T]{',
118 '\t\tdata: val',
119 '\t}',
120 '\tl.node = &node',
121 '}',
122 '',
123 'fn (shared t Topic[T]) add(mut data T) {',
124 '\tlock t {',
125 '\t\tt.list.push(data)',
126 '\t}',
127 '}',
128 '',
129 'fn main() {',
130 '\tshared topic := Topic.new[string]()',
131 "\tmut data := 'something'",
132 '\ttopic.add(mut data)',
133 "\tprintln('done')",
134 '}',
135 ].join_lines()
136 result := run_source_with_timeout('shared_generic_heap_method_call', source) or {
137 assert false, 'shared generic heap method call hung\n${err}'
138 return
139 }
140 assert result.code == 0, result.output
141 assert result.output.contains('done'), result.output
142}
143