Permalink
Browse files

Add initial documentation on SELF-PIPE Trick

  • Loading branch information...
1 parent e44eea4 commit aa5fa6a28c24404e6b9f1898fd882ea690eea152 Ruslan Spivak committed Jun 24, 2012
Showing with 168 additions and 2 deletions.
  1. +89 −2 README.rst
  2. +79 −0 selsigrace.py
View
@@ -117,6 +117,95 @@ an RST as a response, ignores it and tries to write to the socket:
s.send('hello')
socket.error: [Errno 32] Broken pipe
+
+SELF-PIPE Trick
+~~~~~~~~~~~~~~~
+
+If a process needs to monitor several descriptors for I/O and wait
+for the delivery of a signal then a race condition can happen.
+
+Consider the following code excerpt:
+
+::
+
+ GOT_SIGNAL = False
+
+ def handler(signum, frame):
+ global GOT_SIGNAL
+ GOT_SIGNAL = True
+
+ ...
+
+ signal.signal(signal.SIGUSR1, handler)
+
+ # What if the signal arrives at this point?
+
+ try:
+ readables, writables, exceptions = select.select(rlist, wlist, elist)
+ except select.error as e:
+ code, msg = e.args
+ if code == errno.EINTR:
+ if GOT_SIGNAL:
+ print 'Got signal'
+ else:
+ raise
+
+The problem here is that if the *SIGUSR1* is delivered after setting
+the signal handler but before call to *select* then the *select*
+call **will block** and we won't execute our application logic in
+response to the event thus effectively *"missing"* the signal (our
+application logic in this case is printing the message: *Got signal*).
+
+That's an example of possible nasty racing. Let's simulate that with `selsigrace.py <https://github.com/rspivak/csdesign/blob/master/selsigrace.py>`_
+
+Start the program
+
+::
+
+ $ python selsigrace.py
+ PID: 32324
+ Sleep for 10 secs
+
+and send the *USR1* signal to the PID(it's different on every run)
+within the 10 second interval while the process is still sleeping:
+
+::
+
+ $ kill -USR1 32324
+
+
+You should see the program produce additional line of output
+'Wake up and block in "select"' and **block without exiting**, no
+message "Got signal":
+
+::
+
+ $ python selsigrace.py
+ PID: 32324
+ Sleep for 10 secs
+ Wake up and block in "select"
+
+If you send yet another *USR1* signal at this point then the *select*
+will be interrupted and the program will terminate with a message:
+
+::
+
+ $ kill -USR1 32324
+
+::
+
+ $ python selsigrace.py
+ PID: 32324
+ Sleep for 10 secs
+ Wake up and block in "select"
+ Got signal
+
+*Self-Pipe Trick* is used to avoid race conditions when waiting for
+signals and calling *select* on a set of descriptors.
+
+XXX: Code
+
+
Roadmap
-------
@@ -128,8 +217,6 @@ Roadmap
- TCP Prethreaded Server
-- Miscellanea, SELF-PIPE Trick
-
- Miscellanea, **sendfile** system call
- Miscellanea, TCP_CORK socket option
View
@@ -0,0 +1,79 @@
+###############################################################################
+#
+# Copyright (c) 2012 Ruslan Spivak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+
+"""
+Illustrates a problem with a race condition that might happen if a process
+needs to monitor several descriptors for I/O and wait for the delivery of
+a signal.
+"""
+
+__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
+
+import os
+import sys
+import time
+import errno
+import select
+import signal
+
+GOT_SIGNAL = False
+
+
+def handler(signum, frame):
+ global GOT_SIGNAL
+ GOT_SIGNAL = True
+
+
+def main():
+ print 'PID: %s' % os.getpid()
+
+ signal.signal(signal.SIGUSR1, handler)
+
+ # read, write, exception lists with descriptors to poll
+ rlist, wlist, elist = [sys.stdin.fileno()], [], []
+
+ print 'Sleep for 10 secs'
+ time.sleep(10)
+ print 'Wake up and block in "select"'
+
+ #
+ # Nasty racing can happen at this point if the signal arrives before
+ # the call to 'select' - the call won't be interrupted and 'select'
+ # will block
+ #
+
+ # block in select
+ try:
+ readables, writables, exceptions = select.select(rlist, wlist, elist)
+ except select.error as e:
+ code, msg = e.args
+ if code == errno.EINTR:
+ if GOT_SIGNAL:
+ print 'Got signal'
+ else:
+ raise
+
+
+if __name__ == '__main__':
+ main()

0 comments on commit aa5fa6a

Please sign in to comment.