Add a version that uses a lock instead of a queue.
David Blume

David Blume commited on 2019-07-06 11:32:09
Showing 3 changed files, with 83 additions and 0 deletions.

... ...
@@ -75,6 +75,24 @@ No more race conditions! The worker threads mind their own business.
75 75
 
76 76
 The worker threads remain in memory.
77 77
 
78
+## Long Lived Looping Locked Workers
79
+
80
+That was fun using only the synchronized queue class and no locks. But now that
81
+we've got the long lived looping workers that don't need their own queue, let's
82
+replace the print manager with a threading lock.
83
+
84
+![Long Lived Looping Locked Workers](http://dlma.com/images/python_pinger/ping_long_lived_looping_locked_workers.png)
85
+
86
+See the source: **[long\_lived\_looping\_locked\_workers.py](http://git.dlma.com/python_pinger.git/blob/master/long_lived_looping_locked_workers.py)**
87
+
88
+### Upsides
89
+
90
+Got rid of the entire printer thread.
91
+
92
+### Downsides
93
+
94
+Uses a lock, which in more complex applications with multiple locks becomes difficult to reason about.
95
+
78 96
 ## Is it any good?
79 97
 
80 98
 [Yes](https://news.ycombinator.com/item?id=3067434).
... ...
@@ -0,0 +1,65 @@
1
+#!/usr/bin/python3
2
+#
3
+# Since this is meant to be run as a daemon, do not use #!/usr/bin/env python3,
4
+# because the process name would then be python3, not based on __FILE__.
5
+#
6
+# [filename] -d 3 192.168.1.1 192.168.1.12 &
7
+# disown [jobid]
8
+import os
9
+import sys
10
+import subprocess
11
+import time
12
+from argparse import ArgumentParser
13
+import threading
14
+import queue
15
+
16
+printer_lock = threading.Lock()
17
+
18
+def pinger(host, delay):
19
+    """Independent worker thread: repeatedly ping, check, print and sleep."""
20
+    last_results = None
21
+    while True:
22
+        now = time.localtime()
23
+        success = ping(host)
24
+        if success != last_results:
25
+            status = "UP" if success else "DOWN"
26
+            log(f'{time.strftime("%Y-%m-%d %H:%M:%S", now)} {host} {status}')
27
+            last_results = success
28
+        time.sleep(delay)
29
+
30
+
31
+def ping(host):
32
+    """Returns True if host (str) responds to a ping request."""
33
+    return subprocess.call([*_ping_cmd, host], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0
34
+
35
+
36
+def log(*args, **kwargs):
37
+    """Opens the logfile, writes the log, and closes the logfile."""
38
+    # I don't leave the log file open for writing because another process needs access too.
39
+    with printer_lock:
40
+        with open(_logname, 'a') as fp:
41
+            return print(*args, **kwargs, file=fp)
42
+
43
+
44
+if __name__ == '__main__':
45
+    parser = ArgumentParser(description="Repeatedly ping sites. Ex: %(prog)s -d 3 192.168.1.1 192.168.1.12")
46
+    parser.add_argument('-d', '--delay', type=float, default=1.0, help='Delay between pings')
47
+    parser.add_argument('addresses', nargs='+', help='Addresses to ping')
48
+    parser_args = parser.parse_args()
49
+
50
+    _logname = os.path.join(os.path.expanduser('~'), 'log', os.path.basename(sys.argv[0]).replace('py', 'txt'))
51
+
52
+    # Choose the ping parameters appropriate for the platform
53
+    if sys.platform == 'cygwin' or sys.platform == 'win32':
54
+        _ping_cmd = ('ping', '-n', '1', '-w', '2000')
55
+    else:
56
+        _ping_cmd = ('ping', '-c', '1', '-W', '2', '-q')
57
+
58
+    log(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}'
59
+        f' PID={os.getpid()} repeat={parser_args.delay}s Starting')
60
+    for address in parser_args.addresses:
61
+        t = threading.Thread(target=pinger, args=(address, parser_args.delay), daemon=True)
62
+        t.start()
63
+
64
+    # Stay alive forever. Join the last worker thread.
65
+    t.join()
0 66