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
|
## Telling a signal apart from a failure.
##
## Nothing in this suite raises a real EINTR — a test binary has no signals
## worth the name, which is exactly why the app hit this and the tests never
## did. So the errors here are built by hand, and what is checked is the
## judgement: which ones are retried, which are not, and that a retry gives up
## rather than spinning.
import std/[os, unittest]
import std/posix as p
import frq/eintr
proc osError(code: cint, msg: string): ref OSError =
result = newException(OSError, msg)
result.errorCode = code.int32
suite "interrupted":
test "an OSError carrying EINTR is only a signal":
check interrupted(osError(p.EINTR, "Interrupted system call"))
test "any other OSError is a real failure":
check not interrupted(osError(p.ECONNRESET, "Connection reset by peer"))
check not interrupted(osError(p.EPIPE, "Broken pipe"))
test "and so is an error that is not an OSError at all":
check not interrupted(newException(ValueError, "nonsense"))
test "but the words count where the code was lost":
# `httpclient` catches an OSError in places and re-raises its text, which
# keeps the message and drops the code.
check interrupted(newException(IOError, "Interrupted system call"))
suite "retrying":
test "a body that works runs once":
var runs = 0
retrying 3:
runs.inc
check runs == 1
test "one cut short by a signal is run again":
var runs = 0
retrying 3:
runs.inc
if runs < 3: raise osError(p.EINTR, "Interrupted system call")
check runs == 3
test "a real failure is raised at once, not retried":
var runs = 0
expect OSError:
retrying 5:
runs.inc
raise osError(p.ECONNREFUSED, "Connection refused")
check runs == 1
test "and a signal that never stops gives up rather than spinning":
var runs = 0
expect OSError:
retrying 4:
runs.inc
raise osError(p.EINTR, "Interrupted system call")
check runs == 4
suite "restartableSyscalls":
test "adds SA_RESTART without disturbing the handler":
# The flag says what the kernel does to an interrupted syscall. It must
# not change which function runs, or whose signal it is — the profiler
# whose handler this finds is somebody else's.
proc handler(sig: cint) {.noconv.} = discard
var wanted: Sigaction
discard sigemptyset(wanted.sa_mask)
wanted.sa_handler = handler
wanted.sa_flags = 0
check p.sigaction(SIGALRM, wanted, nil) == 0
restartableSyscalls()
var got: Sigaction
check sigactionOf(SIGALRM, got)
check (got.sa_flags and p.SA_RESTART) != 0
check got.sa_handler == handler
test "and leaves one that already has it alone":
proc handler(sig: cint) {.noconv.} = discard
var wanted: Sigaction
discard sigemptyset(wanted.sa_mask)
wanted.sa_handler = handler
wanted.sa_flags = p.SA_RESTART
check p.sigaction(SIGALRM, wanted, nil) == 0
restartableSyscalls()
var got: Sigaction
check sigactionOf(SIGALRM, got)
check got.sa_handler == handler
check (got.sa_flags and p.SA_RESTART) != 0
|