Merge lp:~spiv/bzr/no-sigwinch-583941 into lp:bzr/2.1

Proposed by Andrew Bennetts
Status: Merged
Approved by: Andrew Bennetts
Approved revision: no longer in the source branch.
Merged at revision: 4844
Proposed branch: lp:~spiv/bzr/no-sigwinch-583941
Merge into: lp:bzr/2.1
Diff against target: 204 lines (+66/-40)
4 files modified
NEWS (+7/-1)
bzrlib/osutils.py (+47/-35)
bzrlib/tests/test_osutils.py (+12/-0)
bzrlib/ui/text.py (+0/-4)
To merge this branch: bzr merge lp:~spiv/bzr/no-sigwinch-583941
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
Martin Pool Pending
Martin Packman Pending
Review via email: mp+26118@code.launchpad.net

This proposal supersedes a proposal from 2010-05-26.

Commit message

Remove the SIGWINCH handler to avoid EINTR problems. Instead poll the terminal size as needed. (#583941)

Description of the change

Final, simple fix for EINTR?

This simply removes the SIGWINCH handler introduced in 2.1.0rc1, and adjusts the osutils.terminal_width function accordingly.

I'm a little concerned that the logic inside terminal_width looks somewhat different now, because it tries _terminal_size before it tries $COLUMNS — but, strangely, the SIGWINCH handler had actually been setting $COLUMNS, so I don't think this is actually so different. And I do think the new code is clearer. I'd welcome second opinions though.

Finally, here's the diffstat to whet your appetite:

$ bzr diff | diffstat
 NEWS | 8 +++++++-
 bzrlib/osutils.py | 42 +++++++++---------------------------------
 bzrlib/ui/text.py | 4 ----
 3 files changed, 16 insertions(+), 38 deletions(-)

As with the previous proposal for this, I've tested interactively that e.g. bzr log -n0 from a large HTTP branch continues successfully while the terminal is rapidly resized, unlike before.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : Posted in a previous version of this proposal

DoIt - love to get this behind us

Revision history for this message
Vincent Ladeuil (vila) wrote : Posted in a previous version of this proposal

>>>>> Andrew Bennetts <email address hidden> writes:

    > Andrew Bennetts has proposed merging lp:~spiv/bzr/no-sigwinch-583941 into lp:bzr/2.1.
    > Requested reviews:
    > bzr-core (bzr-core)
    > Related bugs:
    > #583941 program crashes with "bzr: ERROR: socket.error: (4, 'Interrupted system call')"
    > https://bugs.launchpad.net/bugs/583941

    > Final, simple fix for EINTR?

    > This simply removes the SIGWINCH handler introduced in 2.1.0rc1,
    > and adjusts the osutils.terminal_width function accordingly.

    > I'm a little concerned that the logic inside terminal_width looks
    > somewhat different now, because it tries _terminal_size before it
    > tries $COLUMNS — but, strangely, the SIGWINCH handler had actually
    > been setting $COLUMNS, so I don't think this is actually so
    > different.

qblame to the rescue, have a look at 4747.4.3
(revid:<email address hidden>[1]):

,----
| * bzrlib/osutils.py:
| (terminal_width): COLUMNS takes priority on termios.TIOCGWINSZ as
| shown by tests under emacs (on no counter examples are known).
`----

This is indeed a known case where COLUMNS should take precedence. And
the comment you deleted was trying to hint at that. If you find a better
wording, please feel free to update it but don't just delete the hints
about the rationale.

Unfortunately, this couldn't be translated into a test since the tests
are supposed to be runned even without a controlling terminal ;(

We should check the env variable first *at least* when we start.

Now, if SIGWINCH occurs, we can't get an updated value for COLUMNS, so
we have to resort to _terminal_size (even if it's wrong now, it seems
less wrong than keeping a knowingly out-of-date COLUMNS) and setting
COLUMNS was the easiest way to go then.

[1]: Amazingly this revid includes the string 'bug' :)

Revision history for this message
Martin Pool (mbp) wrote : Posted in a previous version of this proposal

That looks nice.

I'd like to check that running eg that log command does not take any longer with this patch.

I guess you can work out with vila what to do under emacs. I would guess that the ioctl just does not work there and so it's enough to use $COLUMNS if the ioctl fails, but it may be more complicated. Perhaps we can add a big comment about this.

review: Approve
Revision history for this message
Andrew Bennetts (spiv) wrote : Posted in a previous version of this proposal

vila and I have had an extensive discussion on IRC. I'll try to write a comprehensive summary soon, but right now it's dinner time, so here's the short version:

1) I'll rework this patch for 2.1 to preserve the 2.1 behaviour as much as possible without using SIGWINCH (i.e. use COLUMNS until we observe the size from TIOCGWINSZ change), to minimise risk of regressions in 2.1.

2) I'll submit another patch for trunk that I think will be a better implementation of terminal_width: try $BZR_COLUMNS, then TIOCGWINSZ, then $COLUMNS in turn, until we find one that is set to a non-zero value. If none are set non-zero, return None. This patch will try to explicitly document the rationale and problematic cases to take into account, because the literature around determining terminal sizes is pretty confusing.

I'm popping this branch back to work-in-progress for now, but expect a resubmission soon.

Revision history for this message
Martin Packman (gz) wrote : Posted in a previous version of this proposal

Looks good to me. There was a lot of detailed discussion on IRC about the specifics of getting the logic maximally compatible. Trying for a minimal change on 2.1 and investigating further for trunk sounds like a solid plan. So, this branch only really needs a tweak to make the initial result of _terminal_size weaker than COLUMNS before landing.

The question of speed has been raised a couple of times. Andrew timed _ioctl_terminal_size at 11μs and that can be trivially improved upon if needed. The common case could just call fcntl.ioctl and do a string comparison, no need for function-level imports or struct packing and unpacking.

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

Here's the patch to remove SIGWINCH with minimal behaviour change for 2.1. I've added some comments that try to explain some of the requirement and rationale that was unclear before.

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

urk, all this to measure what should be a history-independent property
of the environment.

Still, that looks clean and matches my understanding of the
discussion. Nice comment too. Perhaps you should wait until Vincent
gets to check it.

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

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

Martin Pool wrote:
> urk, all this to measure what should be a history-independent property
> of the environment.

Yes :(

> Still, that looks clean and matches my understanding of the
> discussion. Nice comment too. Perhaps you should wait until Vincent
> gets to check it.

I'll do that.

Revision history for this message
Robert Collins (lifeless) wrote :

Vincent, when you review it, if you're happy please *just land it* in 2.1 - so that I can release 2.1.2 tomorrow.

Cheers,
Rob

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

btw, Martin P asked earlier:

> I'd like to check that running eg that log command does not take any longer with this patch.

I did some manual testing of "bzr log -r-100.. bzr.dev" (753 lines of output), and there's no noticeable difference. So that's good.

(As an aside, I notice that trunk is a little quicker than 2.1 on that task, hooray! About 320ms vs 340ms on my system.)

Revision history for this message
Vincent Ladeuil (vila) wrote :

Fine. Great job with the comments.

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

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-05-21 01:55:34 +0000
3+++ NEWS 2010-05-27 04:04:24 +0000
4@@ -20,6 +20,12 @@
5 * ``bzr switch`` does not die if a ConfigurableFileMerger is used.
6 (Aaron Bentley, #559436)
7
8+* Do not register a SIGWINCH signal handler, instead just poll for the
9+ terminal width as needed. This avoids the "Interrupted System Call"
10+ problems that occur on POSIX with all currently released versions of
11+ Python.
12+ (Andrew Bennetts, #583941)
13+
14 * Fixed ``AssertionError`` when accessing smart servers running Bazaar
15 versions before 1.6.
16 (Andrew Bennetts, #528041)
17@@ -27,7 +33,7 @@
18 * Reset ``siginterrupt`` flag to False every time we handle a signal
19 installed with ``set_signal_handler(..., restart_syscall=True)`` (from
20 ``bzrlib.osutils``. Reduces the likelihood of "Interrupted System Call"
21- errors after two window resizes.
22+ errors compared to registering ``signal.signal`` directly.
23 (Andrew Bennetts)
24
25 * Reduce peak memory by one copy of compressed text.
26
27=== modified file 'bzrlib/osutils.py'
28--- bzrlib/osutils.py 2010-04-30 06:27:15 +0000
29+++ bzrlib/osutils.py 2010-05-27 04:04:24 +0000
30@@ -1385,6 +1385,12 @@
31 terminal_width() returns None.
32 """
33
34+# Keep some state so that terminal_width can detect if _terminal_size has
35+# returned a different size since the process started. See docstring and
36+# comments of terminal_width for details.
37+# _terminal_size_state has 3 possible values: no_data, unchanged, and changed.
38+_terminal_size_state = 'no_data'
39+_first_terminal_size = None
40
41 def terminal_width():
42 """Return terminal width.
43@@ -1394,20 +1400,34 @@
44 The rules are:
45 - if BZR_COLUMNS is set, returns its value
46 - if there is no controlling terminal, returns None
47+ - query the OS, if the queried size has changed since the last query,
48+ return its value,
49 - if COLUMNS is set, returns its value,
50+ - if the OS has a value (even though it's never changed), return its value.
51
52 From there, we need to query the OS to get the size of the controlling
53 terminal.
54
55- Unices:
56+ On Unices we query the OS by:
57 - get termios.TIOCGWINSZ
58 - if an error occurs or a negative value is obtained, returns None
59
60- Windows:
61-
62+ On Windows we query the OS by:
63 - win32utils.get_console_size() decides,
64 - returns None on error (provided default value)
65 """
66+ # Note to implementors: if changing the rules for determining the width,
67+ # make sure you've considered the behaviour in these cases:
68+ # - M-x shell in emacs, where $COLUMNS is set and TIOCGWINSZ returns 0,0.
69+ # - bzr log | less, in bash, where $COLUMNS not set and TIOCGWINSZ returns
70+ # 0,0.
71+ # - (add more interesting cases here, if you find any)
72+ # Some programs implement "Use $COLUMNS (if set) until SIGWINCH occurs",
73+ # but we don't want to register a signal handler because it is impossible
74+ # to do so without risking EINTR errors in Python <= 2.6.5 (see
75+ # <http://bugs.python.org/issue8354>). Instead we check TIOCGWINSZ every
76+ # time so we can notice if the reported size has changed, which should have
77+ # a similar effect.
78
79 # If BZR_COLUMNS is set, take it, user is always right
80 try:
81@@ -1416,24 +1436,39 @@
82 pass
83
84 isatty = getattr(sys.stdout, 'isatty', None)
85- if isatty is None or not isatty():
86+ if isatty is None or not isatty():
87 # Don't guess, setting BZR_COLUMNS is the recommended way to override.
88 return None
89
90- # If COLUMNS is set, take it, the terminal knows better (even inside a
91- # given terminal, the application can decide to set COLUMNS to a lower
92- # value (splitted screen) or a bigger value (scroll bars))
93+ # Query the OS
94+ width, height = os_size = _terminal_size(None, None)
95+ global _first_terminal_size, _terminal_size_state
96+ if _terminal_size_state == 'no_data':
97+ _first_terminal_size = os_size
98+ _terminal_size_state = 'unchanged'
99+ elif (_terminal_size_state == 'unchanged' and
100+ _first_terminal_size != os_size):
101+ _terminal_size_state = 'changed'
102+
103+ # If the OS claims to know how wide the terminal is, and this value has
104+ # ever changed, use that.
105+ if _terminal_size_state == 'changed':
106+ if width is not None and width > 0:
107+ return width
108+
109+ # If COLUMNS is set, use it.
110 try:
111 return int(os.environ['COLUMNS'])
112 except (KeyError, ValueError):
113 pass
114
115- width, height = _terminal_size(None, None)
116- if width <= 0:
117- # Consider invalid values as meaning no width
118- return None
119+ # Finally, use an unchanged size from the OS, if we have one.
120+ if _terminal_size_state == 'unchanged':
121+ if width is not None and width > 0:
122+ return width
123
124- return width
125+ # The width could not be determined.
126+ return None
127
128
129 def _win32_terminal_size(width, height):
130@@ -1466,29 +1501,6 @@
131 _terminal_size = _ioctl_terminal_size
132
133
134-def _terminal_size_changed(signum, frame):
135- """Set COLUMNS upon receiving a SIGnal for WINdow size CHange."""
136- width, height = _terminal_size(None, None)
137- if width is not None:
138- os.environ['COLUMNS'] = str(width)
139-
140-
141-_registered_sigwinch = False
142-
143-def watch_sigwinch():
144- """Register for SIGWINCH, once and only once."""
145- global _registered_sigwinch
146- if not _registered_sigwinch:
147- if sys.platform == 'win32':
148- # Martin (gz) mentioned WINDOW_BUFFER_SIZE_RECORD from
149- # ReadConsoleInput but I've no idea how to plug that in
150- # the current design -- vila 20091216
151- pass
152- else:
153- set_signal_handler(signal.SIGWINCH, _terminal_size_changed)
154- _registered_sigwinch = True
155-
156-
157 def supports_executable():
158 return sys.platform != "win32"
159
160
161=== modified file 'bzrlib/tests/test_osutils.py'
162--- bzrlib/tests/test_osutils.py 2010-02-17 17:11:16 +0000
163+++ bzrlib/tests/test_osutils.py 2010-05-27 04:04:24 +0000
164@@ -1928,6 +1928,18 @@
165
166 class TestTerminalWidth(tests.TestCase):
167
168+ def setUp(self):
169+ tests.TestCase.setUp(self)
170+ self._orig_terminal_size_state = osutils._terminal_size_state
171+ self._orig_first_terminal_size = osutils._first_terminal_size
172+ self.addCleanup(self.restore_osutils_globals)
173+ osutils._terminal_size_state = 'no_data'
174+ osutils._first_terminal_size = None
175+
176+ def restore_osutils_globals(self):
177+ osutils._terminal_size_state = self._orig_terminal_size_state
178+ osutils._first_terminal_size = self._orig_first_terminal_size
179+
180 def replace_stdout(self, new):
181 orig_stdout = sys.stdout
182 def restore():
183
184=== modified file 'bzrlib/ui/text.py'
185--- bzrlib/ui/text.py 2010-03-23 06:45:56 +0000
186+++ bzrlib/ui/text.py 2010-05-27 04:04:24 +0000
187@@ -37,8 +37,6 @@
188
189 """)
190
191-from bzrlib.osutils import watch_sigwinch
192-
193 from bzrlib.ui import (
194 UIFactory,
195 NullProgressView,
196@@ -62,8 +60,6 @@
197 self.stderr = stderr
198 # paints progress, network activity, etc
199 self._progress_view = self.make_progress_view()
200- # hook up the signals to watch for terminal size changes
201- watch_sigwinch()
202
203 def be_quiet(self, state):
204 if state and not self._quiet:

Subscribers

People subscribed via source and target branches