nandi/frqpublic Fork 0
23846db48565a450ee8a6a689892db23663ad48e
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.

Restartable syscalls, because retrying cannot win dcd31d9 · on 23846db48565a450ee8a6a689892db23663ad48e · nandi · 13h ago
teintr.nim · 90 lines · 3.1 KBNim Blame HistoryRaw
 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