nandi/frqpublic Fork 0
07a23efce61c21f57e770a21f841752fcfd97520
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

teintr.nim · 90 lines · 3.1 KBNim Blame HistoryRaw
A signal is not a failure abb8e36 nandi 22h ago1## Telling a signal apart from a failure.
2##
3## Nothing in this suite raises a real EINTR — a test binary has no signals
4## worth the name, which is exactly why the app hit this and the tests never
5## did. So the errors here are built by hand, and what is checked is the
6## judgement: which ones are retried, which are not, and that a retry gives up
7## rather than spinning.
8
9import std/[os, unittest]
10import std/posix as p
11import frq/eintr
12
13proc osError(code: cint, msg: string): ref OSError =
14 result = newException(OSError, msg)
15 result.errorCode = code.int32
16
17suite "interrupted":
18 test "an OSError carrying EINTR is only a signal":
19 check interrupted(osError(p.EINTR, "Interrupted system call"))
20 test "any other OSError is a real failure":
21 check not interrupted(osError(p.ECONNRESET, "Connection reset by peer"))
22 check not interrupted(osError(p.EPIPE, "Broken pipe"))
23 test "and so is an error that is not an OSError at all":
24 check not interrupted(newException(ValueError, "nonsense"))
25 test "but the words count where the code was lost":
26 # `httpclient` catches an OSError in places and re-raises its text, which
27 # keeps the message and drops the code.
28 check interrupted(newException(IOError, "Interrupted system call"))
29
30suite "retrying":
31 test "a body that works runs once":
32 var runs = 0
33 retrying 3:
34 runs.inc
35 check runs == 1
36
37 test "one cut short by a signal is run again":
38 var runs = 0
39 retrying 3:
40 runs.inc
41 if runs < 3: raise osError(p.EINTR, "Interrupted system call")
42 check runs == 3
43
44 test "a real failure is raised at once, not retried":
45 var runs = 0
46 expect OSError:
47 retrying 5:
48 runs.inc
49 raise osError(p.ECONNREFUSED, "Connection refused")
50 check runs == 1
51
52 test "and a signal that never stops gives up rather than spinning":
53 var runs = 0
54 expect OSError:
55 retrying 4:
56 runs.inc
57 raise osError(p.EINTR, "Interrupted system call")
58 check runs == 4
Restartable syscalls, because retrying cannot win dcd31d9 nandi 15h ago59
60suite "restartableSyscalls":
61 test "adds SA_RESTART without disturbing the handler":
62 # The flag says what the kernel does to an interrupted syscall. It must
63 # not change which function runs, or whose signal it is — the profiler
64 # whose handler this finds is somebody else's.
65 proc handler(sig: cint) {.noconv.} = discard
66 var wanted: Sigaction
67 discard sigemptyset(wanted.sa_mask)
68 wanted.sa_handler = handler
69 wanted.sa_flags = 0
70 check p.sigaction(SIGALRM, wanted, nil) == 0
71
72 restartableSyscalls()
73
74 var got: Sigaction
75 check sigactionOf(SIGALRM, got)
76 check (got.sa_flags and p.SA_RESTART) != 0
77 check got.sa_handler == handler
78
79 test "and leaves one that already has it alone":
80 proc handler(sig: cint) {.noconv.} = discard
81 var wanted: Sigaction
82 discard sigemptyset(wanted.sa_mask)
83 wanted.sa_handler = handler
84 wanted.sa_flags = p.SA_RESTART
85 check p.sigaction(SIGALRM, wanted, nil) == 0
86 restartableSyscalls()
87 var got: Sigaction
88 check sigactionOf(SIGALRM, got)
89 check got.sa_handler == handler
90 check (got.sa_flags and p.SA_RESTART) != 0