Merge lp:~spiv/bzr/i-hate-signals into lp:bzr

Proposed by Andrew Bennetts
Status: Merged
Merged at revision: not available
Proposed branch: lp:~spiv/bzr/i-hate-signals
Merge into: lp:bzr
Diff against target: 51 lines (+23/-7)
2 files modified
NEWS (+6/-0)
bzrlib/osutils.py (+17/-7)
To merge this branch: bzr merge lp:~spiv/bzr/i-hate-signals
Reviewer Review Type Date Requested Status
Martin Packman (community) Approve
bzr-core Pending
Review via email: mp+23004@code.launchpad.net

Description of the change

Python resets the siginterrupt flag when it invokes a pure-python signal handler. So this patch sets it back again.

This seems to be a bug in upstream Python (in their docs if nothing else), I'll submit a bug report soon.

To post a comment you must log in.
Revision history for this message
Martin Packman (gz) wrote :

Abstaining so I get CCed until I have a spare moment to look at this properly.

Of somewhat related interest on python-dev today:
<http://mail.python.org/pipermail/python-dev/2010-April/099165.html>
<http://bugs.python.org/issue7978>

review: Abstain
Revision history for this message
Andrew Bennetts (spiv) wrote :

I filed <http://bugs.python.org/issue8354> about this.

I should clarify that this patch doesn't fully workaround that bug, but it helps in the case that there's some IO between delivery of signals (so that my signal handler gets a chance to redo the siginterrupt call that Python undoes). The only relevant signal for current bzr is SIGWINCH, so IO just needs to happen more often than a window resize event, which is plausible — it did fix the immediate issue that I ran into as a user.

Revision history for this message
Martin Packman (gz) wrote :

Okay, been through this, the upstream bug, and the signal module C code.
Diagnosis looks fine, as does the proposed patch to the C signal handler in the Python source, would be good to get that landed.

One thing I'm not clear on is how effective the workaround in this merge will be. As the Python handler won't run till the IO function returns, and we've prevented it returning early, there can (and in common cases will be) whole seconds elapsed before say, `sock.read(65536)` completes. Do window managers fire multiple SIGWINCHes if a terminal emulator is dragged wider, or do they tend to wait for the release?

review: Needs Information
Revision history for this message
Andrew Bennetts (spiv) wrote :

It probably depends on the window manager: some have you drag an outline (or static snapshot) of the window, some do "live" resizing (and some can be configured either way). For me the most common way I resize a terminal is by maximising or unmaximising a GNOME Terminal, which thankfully only seems to trigger one SIGWINCH per (un)maximise.

There can be other ways to trigger a resize too, like opening a second tab in a maximised GNOME Terminal window: the creation of the tab widget consumes some screen real estate, so that's a terminal resize too. Opening a second tab and (un)maximising are relatively frequent operations for me.

I'm sure there are many real-world cases this workaround can't fix, sadly. But I can say that it definitely improved behaviour for me in some real-world cases, e.g. a 'bzr pull' over plain HTTP.

Revision history for this message
Martin Pool (mbp) wrote :

Tangentially, we do have the option to say that sigwinch is just too
much trouble and instead poll the window size when we need it. But
polling every time we update progress may be too often, and doing it
infrequently may cause mess when the window is resized.

Alternatively we could hook that signal from C, and make it just
update a global accessible from python.

--
Martin <http://launchpad.net/~mbp/>

Revision history for this message
Martin Packman (gz) wrote :

Okay, so there are common situations this workaround-for-a-workaround will help with then. Can't test here, but diff looks fine to me. Oh, and I sympathise with the branch name.

review: Approve
Revision history for this message
Andrew Bennetts (spiv) wrote :

Martin P: I think avoiding SIGWINCH, or at least a Python handler for it, is worth exploring. Although there's a reasonable-looking fix attached to the upstream bug, which would make the existing Python SIGWINCH handler just fine.

In the meantime, I'll merge this partial workaround because it is better than the status quo.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-04-14 04:35:47 +0000
3+++ NEWS 2010-04-14 06:49:37 +0000
4@@ -40,6 +40,12 @@
5 which is not installed any more" error.
6 (Martin Pool, James Westby, #528114)
7
8+* Reset ``siginterrupt`` flag to False every time we handle a signal
9+ installed with ``set_signal_handler(..., restart_syscall=True)`` (from
10+ ``bzrlib.osutils``. Reduces the likelihood of "Interrupted System Call"
11+ errors after two window resizes.
12+ (Andrew Bennetts)
13+
14 * When invoked with a range revision, ``bzr log`` doesn't show revisions
15 that are not part of the ancestry anymore.
16 (Vincent Ladeuil, #474807)
17
18=== modified file 'bzrlib/osutils.py'
19--- bzrlib/osutils.py 2010-03-26 04:47:45 +0000
20+++ bzrlib/osutils.py 2010-04-14 06:49:37 +0000
21@@ -1365,15 +1365,25 @@
22 False)`). May be ignored if the feature is not available on this
23 platform or Python version.
24 """
25- old_handler = signal.signal(signum, handler)
26+ try:
27+ siginterrupt = signal.siginterrupt
28+ except AttributeError:
29+ # siginterrupt doesn't exist on this platform, or for this version
30+ # of Python.
31+ siginterrupt = lambda signum, flag: None
32 if restart_syscall:
33- try:
34- siginterrupt = signal.siginterrupt
35- except AttributeError: # siginterrupt doesn't exist on this platform, or for this version of
36- # Python.
37- pass
38- else:
39+ def sig_handler(*args):
40+ # Python resets the siginterrupt flag when a signal is
41+ # received. <http://bugs.python.org/issue8354>
42+ # As a workaround for some cases, set it back the way we want it.
43 siginterrupt(signum, False)
44+ # Now run the handler function passed to set_signal_handler.
45+ handler(*args)
46+ else:
47+ sig_handler = handler
48+ old_handler = signal.signal(signum, sig_handler)
49+ if restart_syscall:
50+ siginterrupt(signum, False)
51 return old_handler
52
53