|
1 | 1 | // This code is released under the MIT License |
2 | | -// Copyright (c) 2020 Marco Molteni and the timeit contributors. |
| 2 | +// Copyright (c) 2020-2023 Marco Molteni and the timeit contributors. |
3 | 3 |
|
4 | 4 | package main |
5 | 5 |
|
6 | 6 | import ( |
7 | | - "flag" |
8 | | - "fmt" |
9 | 7 | "os" |
10 | | - "os/signal" |
11 | | - "time" |
12 | | -) |
13 | | - |
14 | | -const usage = `sleepit: sleep for the specified duration, optionally handling signals |
15 | | -When the line "sleepit: ready" is printed, it means that it is safe to send signals to it |
16 | | -
|
17 | | -Usage: sleepit <command> [<args>] |
18 | 8 |
|
19 | | -Commands |
20 | | -
|
21 | | - default Use default action: on reception of SIGINT terminate abruptly |
22 | | - handle Handle signals: on reception of SIGINT perform cleanup before exiting |
23 | | - version Show the sleepit version` |
24 | | - |
25 | | -var ( |
26 | | - // Filled by the linker. |
27 | | - fullVersion = "unknown" // example: v0.0.9-8-g941583d027-dirty |
| 9 | + "github.com/marco-m/timeit/pkg/sleepit" |
28 | 10 | ) |
29 | 11 |
|
30 | 12 | func main() { |
31 | | - os.Exit(run(os.Args[1:])) |
32 | | -} |
33 | | - |
34 | | -func run(args []string) int { |
35 | | - if len(args) < 1 { |
36 | | - fmt.Fprintln(os.Stderr, usage) |
37 | | - return 2 |
38 | | - } |
39 | | - |
40 | | - defaultCmd := flag.NewFlagSet("default", flag.ExitOnError) |
41 | | - defaultSleep := defaultCmd.Duration("sleep", 5*time.Second, "Sleep duration") |
42 | | - |
43 | | - handleCmd := flag.NewFlagSet("handle", flag.ExitOnError) |
44 | | - handleSleep := handleCmd.Duration("sleep", 5*time.Second, "Sleep duration") |
45 | | - handleCleanup := handleCmd.Duration("cleanup", 5*time.Second, "Cleanup duration") |
46 | | - handleTermAfter := handleCmd.Int("term-after", 0, |
47 | | - "Terminate immediately after `N` signals.\n"+ |
48 | | - "Default is to terminate only when the cleanup phase has completed.") |
49 | | - |
50 | | - versionCmd := flag.NewFlagSet("version", flag.ExitOnError) |
51 | | - |
52 | | - switch args[0] { |
53 | | - |
54 | | - case "default": |
55 | | - defaultCmd.Parse(args[1:]) |
56 | | - if len(defaultCmd.Args()) > 0 { |
57 | | - fmt.Fprintf(os.Stderr, "default: unexpected arguments: %v\n", defaultCmd.Args()) |
58 | | - return 2 |
59 | | - } |
60 | | - return supervisor(*defaultSleep, 0, 0, nil) |
61 | | - |
62 | | - case "handle": |
63 | | - handleCmd.Parse(args[1:]) |
64 | | - if *handleTermAfter == 1 { |
65 | | - fmt.Fprintf(os.Stderr, "handle: term-after cannot be 1\n") |
66 | | - return 2 |
67 | | - } |
68 | | - if len(handleCmd.Args()) > 0 { |
69 | | - fmt.Fprintf(os.Stderr, "handle: unexpected arguments: %v\n", handleCmd.Args()) |
70 | | - return 2 |
71 | | - } |
72 | | - sigCh := make(chan os.Signal, 1) |
73 | | - signal.Notify(sigCh, os.Interrupt) // Ctrl-C -> SIGINT |
74 | | - return supervisor(*handleSleep, *handleCleanup, *handleTermAfter, sigCh) |
75 | | - |
76 | | - case "version": |
77 | | - versionCmd.Parse(args[1:]) |
78 | | - if len(versionCmd.Args()) > 0 { |
79 | | - fmt.Fprintf(os.Stderr, "version: unexpected arguments: %v\n", versionCmd.Args()) |
80 | | - return 2 |
81 | | - } |
82 | | - fmt.Printf("sleepit version %s\n", fullVersion) |
83 | | - return 0 |
84 | | - |
85 | | - default: |
86 | | - fmt.Fprintln(os.Stderr, usage) |
87 | | - return 2 |
88 | | - } |
89 | | -} |
90 | | - |
91 | | -func supervisor( |
92 | | - sleep time.Duration, |
93 | | - cleanup time.Duration, |
94 | | - termAfter int, |
95 | | - sigCh <-chan os.Signal, |
96 | | -) int { |
97 | | - fmt.Printf("sleepit: ready\n") |
98 | | - fmt.Printf("sleepit: PID=%d sleep=%v cleanup=%v\n", |
99 | | - os.Getpid(), sleep, cleanup) |
100 | | - |
101 | | - cancelWork := make(chan struct{}) |
102 | | - workerDone := worker(cancelWork, sleep, "work") |
103 | | - |
104 | | - cancelCleaner := make(chan struct{}) |
105 | | - var cleanerDone <-chan struct{} |
106 | | - |
107 | | - sigCount := 0 |
108 | | - for { |
109 | | - select { |
110 | | - case sig := <-sigCh: |
111 | | - sigCount++ |
112 | | - fmt.Printf("sleepit: got signal=%s count=%d\n", sig, sigCount) |
113 | | - if sigCount == 1 { |
114 | | - // since `cancelWork` is unbuffered, sending will be synchronous: |
115 | | - // we are ensured that the worker has terminated before starting cleanup. |
116 | | - // This is important in some real-life situations. |
117 | | - cancelWork <- struct{}{} |
118 | | - cleanerDone = worker(cancelCleaner, cleanup, "cleanup") |
119 | | - } |
120 | | - if sigCount == termAfter { |
121 | | - cancelCleaner <- struct{}{} |
122 | | - return 4 |
123 | | - } |
124 | | - case <-workerDone: |
125 | | - return 0 |
126 | | - case <-cleanerDone: |
127 | | - return 3 |
128 | | - } |
129 | | - } |
130 | | -} |
131 | | - |
132 | | -// Start a worker goroutine and return immediately a `workerDone` channel. |
133 | | -// The goroutine will prepend its prints with the prefix `name`. |
134 | | -// The goroutine will simulate some work and will terminate when one of the following |
135 | | -// conditions happens: |
136 | | -// 1. When `howlong` is elapsed. This case will be signaled on the `workerDone` channel. |
137 | | -// 2. When something happens on channel `canceled`. Note that this simulates real-life, |
138 | | -// so cancellation is not instantaneous: if the caller wants a synchronous cancel, |
139 | | -// it should send a message; if instead it wants an asynchronous cancel, it should |
140 | | -// close the channel. |
141 | | -func worker( |
142 | | - canceled <-chan struct{}, |
143 | | - howlong time.Duration, |
144 | | - name string, |
145 | | -) <-chan struct{} { |
146 | | - workerDone := make(chan struct{}) |
147 | | - deadline := time.Now().Add(howlong) |
148 | | - go func() { |
149 | | - fmt.Printf("sleepit: %s started\n", name) |
150 | | - for { |
151 | | - select { |
152 | | - case <-canceled: |
153 | | - fmt.Printf("sleepit: %s canceled\n", name) |
154 | | - return |
155 | | - default: |
156 | | - if doSomeWork(deadline) { |
157 | | - fmt.Printf("sleepit: %s done\n", name) // <== NOTE THIS LINE |
158 | | - workerDone <- struct{}{} |
159 | | - return |
160 | | - } |
161 | | - } |
162 | | - } |
163 | | - }() |
164 | | - return workerDone |
165 | | -} |
166 | | - |
167 | | -// Do some work and then return, so that the caller can decide wether to continue or not. |
168 | | -// Return true when all work is done. |
169 | | -func doSomeWork(deadline time.Time) bool { |
170 | | - if time.Now().After(deadline) { |
171 | | - return true |
172 | | - } |
173 | | - timeout := 100 * time.Millisecond |
174 | | - time.Sleep(timeout) |
175 | | - return false |
| 13 | + os.Exit(sleepit.Main()) |
176 | 14 | } |
0 commit comments