~ubuntu-branches/ubuntu/wily/pyzmq/wily

« back to all changes in this revision

Viewing changes to zmq/eventloop/stack_context.py

  • Committer: Package Import Robot
  • Author(s): Julian Taylor
  • Date: 2013-02-24 19:23:15 UTC
  • mfrom: (1.2.1) (9 sid)
  • mto: This revision was merged to the branch mainline in revision 10.
  • Revision ID: package-import@ubuntu.com-20130224192315-qhmwp3m3ymk8r60d
Tags: 2.2.0.1-1
* New upstream release
* relicense debian packaging to LGPL-3
* update watch file to use github directly
  thanks to Bart Martens for the file
* add autopkgtests
* drop obsolete DM-Upload-Allowed
* bump standard to 3.9.4, no changes required

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
1
#
3
2
# Copyright 2010 Facebook
4
3
#
29
28
that transfer control from one context to another (e.g. AsyncHTTPClient
30
29
itself, IOLoop, thread pools, etc).
31
30
 
32
 
Example usage:
 
31
Example usage::
 
32
 
33
33
    @contextlib.contextmanager
34
34
    def die_on_error():
35
35
        try:
36
36
            yield
37
 
        except:
 
37
        except Exception:
38
38
            logging.error("exception in asynchronous operation",exc_info=True)
39
39
            sys.exit(1)
40
40
 
44
44
        # in the ioloop.
45
45
        http_client.fetch(url, callback)
46
46
    ioloop.start()
 
47
 
 
48
Most applications shouln't have to work with `StackContext` directly.
 
49
Here are a few rules of thumb for when it's necessary:
 
50
 
 
51
* If you're writing an asynchronous library that doesn't rely on a
 
52
  stack_context-aware library like `tornado.ioloop` or `tornado.iostream`
 
53
  (for example, if you're writing a thread pool), use
 
54
  `stack_context.wrap()` before any asynchronous operations to capture the
 
55
  stack context from where the operation was started.
 
56
 
 
57
* If you're writing an asynchronous library that has some shared
 
58
  resources (such as a connection pool), create those shared resources
 
59
  within a ``with stack_context.NullContext():`` block.  This will prevent
 
60
  ``StackContexts`` from leaking from one request to another.
 
61
 
 
62
* If you want to write something like an exception handler that will
 
63
  persist across asynchronous calls, create a new `StackContext` (or
 
64
  `ExceptionStackContext`), and make your asynchronous calls in a ``with``
 
65
  block that references your `StackContext`.
47
66
'''
48
67
 
49
 
from __future__ import with_statement
 
68
from __future__ import absolute_import, division, with_statement
50
69
 
51
70
import contextlib
52
71
import functools
53
 
import logging
 
72
import sys
54
73
import threading
55
74
 
56
75
try:
63
82
        self.contexts = ()
64
83
_state = _State()
65
84
 
66
 
@contextlib.contextmanager
67
 
def StackContext(context_factory):
 
85
 
 
86
class StackContext(object):
68
87
    '''Establishes the given context as a StackContext that will be transferred.
69
88
 
70
89
    Note that the parameter is a callable that returns a context
71
90
    manager, not the context itself.  That is, where for a
72
 
    non-transferable context manager you would say
 
91
    non-transferable context manager you would say::
 
92
 
73
93
      with my_context():
74
 
    StackContext takes the function itself rather than its result:
 
94
 
 
95
    StackContext takes the function itself rather than its result::
 
96
 
75
97
      with StackContext(my_context):
76
98
    '''
77
 
    old_contexts = _state.contexts
78
 
    try:
79
 
        _state.contexts = old_contexts + (context_factory,)
80
 
        with context_factory():
81
 
            yield
82
 
    finally:
83
 
        _state.contexts = old_contexts
84
 
 
85
 
@contextlib.contextmanager
86
 
def NullContext():
 
99
    def __init__(self, context_factory):
 
100
        self.context_factory = context_factory
 
101
 
 
102
    # Note that some of this code is duplicated in ExceptionStackContext
 
103
    # below.  ExceptionStackContext is more common and doesn't need
 
104
    # the full generality of this class.
 
105
    def __enter__(self):
 
106
        self.old_contexts = _state.contexts
 
107
        # _state.contexts is a tuple of (class, arg) pairs
 
108
        _state.contexts = (self.old_contexts +
 
109
                           ((StackContext, self.context_factory),))
 
110
        try:
 
111
            self.context = self.context_factory()
 
112
            self.context.__enter__()
 
113
        except Exception:
 
114
            _state.contexts = self.old_contexts
 
115
            raise
 
116
 
 
117
    def __exit__(self, type, value, traceback):
 
118
        try:
 
119
            return self.context.__exit__(type, value, traceback)
 
120
        finally:
 
121
            _state.contexts = self.old_contexts
 
122
 
 
123
 
 
124
class ExceptionStackContext(object):
 
125
    '''Specialization of StackContext for exception handling.
 
126
 
 
127
    The supplied exception_handler function will be called in the
 
128
    event of an uncaught exception in this context.  The semantics are
 
129
    similar to a try/finally clause, and intended use cases are to log
 
130
    an error, close a socket, or similar cleanup actions.  The
 
131
    exc_info triple (type, value, traceback) will be passed to the
 
132
    exception_handler function.
 
133
 
 
134
    If the exception handler returns true, the exception will be
 
135
    consumed and will not be propagated to other exception handlers.
 
136
    '''
 
137
    def __init__(self, exception_handler):
 
138
        self.exception_handler = exception_handler
 
139
 
 
140
    def __enter__(self):
 
141
        self.old_contexts = _state.contexts
 
142
        _state.contexts = (self.old_contexts +
 
143
                           ((ExceptionStackContext, self.exception_handler),))
 
144
 
 
145
    def __exit__(self, type, value, traceback):
 
146
        try:
 
147
            if type is not None:
 
148
                return self.exception_handler(type, value, traceback)
 
149
        finally:
 
150
            _state.contexts = self.old_contexts
 
151
 
 
152
 
 
153
class NullContext(object):
87
154
    '''Resets the StackContext.
88
155
 
89
156
    Useful when creating a shared resource on demand (e.g. an AsyncHTTPClient)
90
157
    where the stack that caused the creating is not relevant to future
91
158
    operations.
92
159
    '''
93
 
    old_contexts = _state.contexts
94
 
    try:
 
160
    def __enter__(self):
 
161
        self.old_contexts = _state.contexts
95
162
        _state.contexts = ()
96
 
        yield
97
 
    finally:
98
 
        _state.contexts = old_contexts
 
163
 
 
164
    def __exit__(self, type, value, traceback):
 
165
        _state.contexts = self.old_contexts
 
166
 
 
167
 
 
168
class _StackContextWrapper(functools.partial):
 
169
    pass
 
170
 
99
171
 
100
172
def wrap(fn):
101
 
    '''Returns a callable object that will resore the current StackContext
 
173
    '''Returns a callable object that will restore the current StackContext
102
174
    when executed.
103
175
 
104
176
    Use this whenever saving a callback to be executed later in a
105
177
    different execution context (either in a different thread or
106
178
    asynchronously in the same thread).
107
179
    '''
108
 
    if fn is None:
109
 
      return None
 
180
    if fn is None or fn.__class__ is _StackContextWrapper:
 
181
        return fn
110
182
    # functools.wraps doesn't appear to work on functools.partial objects
111
183
    #@functools.wraps(fn)
 
184
 
112
185
    def wrapped(callback, contexts, *args, **kwargs):
 
186
        if contexts is _state.contexts or not contexts:
 
187
            callback(*args, **kwargs)
 
188
            return
 
189
        if not _state.contexts:
 
190
            new_contexts = [cls(arg) for (cls, arg) in contexts]
113
191
        # If we're moving down the stack, _state.contexts is a prefix
114
192
        # of contexts.  For each element of contexts not in that prefix,
115
193
        # create a new StackContext object.
116
194
        # If we're moving up the stack (or to an entirely different stack),
117
195
        # _state.contexts will have elements not in contexts.  Use
118
196
        # NullContext to clear the state and then recreate from contexts.
119
 
        if (len(_state.contexts) > len(contexts) or
120
 
            any(a is not b
 
197
        elif (len(_state.contexts) > len(contexts) or
 
198
            any(a[1] is not b[1]
121
199
                for a, b in izip(_state.contexts, contexts))):
122
200
            # contexts have been removed or changed, so start over
123
201
            new_contexts = ([NullContext()] +
124
 
                            [StackContext(c) for c in contexts])
 
202
                            [cls(arg) for (cls,arg) in contexts])
125
203
        else:
126
 
            new_contexts = [StackContext(c)
127
 
                            for c in contexts[len(_state.contexts):]]
128
 
        if new_contexts:
129
 
            with contextlib.nested(*new_contexts):
 
204
            new_contexts = [cls(arg)
 
205
                            for (cls, arg) in contexts[len(_state.contexts):]]
 
206
        if len(new_contexts) > 1:
 
207
            with _nested(*new_contexts):
 
208
                callback(*args, **kwargs)
 
209
        elif new_contexts:
 
210
            with new_contexts[0]:
130
211
                callback(*args, **kwargs)
131
212
        else:
132
213
            callback(*args, **kwargs)
133
 
    if getattr(fn, 'stack_context_wrapped', False):
134
 
        return fn
135
 
    contexts = _state.contexts
136
 
    result = functools.partial(wrapped, fn, contexts)
137
 
    result.stack_context_wrapped = True
138
 
    return result
 
214
    if _state.contexts:
 
215
        return _StackContextWrapper(wrapped, fn, _state.contexts)
 
216
    else:
 
217
        return _StackContextWrapper(fn)
 
218
 
 
219
 
 
220
@contextlib.contextmanager
 
221
def _nested(*managers):
 
222
    """Support multiple context managers in a single with-statement.
 
223
 
 
224
    Copied from the python 2.6 standard library.  It's no longer present
 
225
    in python 3 because the with statement natively supports multiple
 
226
    context managers, but that doesn't help if the list of context
 
227
    managers is not known until runtime.
 
228
    """
 
229
    exits = []
 
230
    vars = []
 
231
    exc = (None, None, None)
 
232
    try:
 
233
        for mgr in managers:
 
234
            exit = mgr.__exit__
 
235
            enter = mgr.__enter__
 
236
            vars.append(enter())
 
237
            exits.append(exit)
 
238
        yield vars
 
239
    except:
 
240
        exc = sys.exc_info()
 
241
    finally:
 
242
        while exits:
 
243
            exit = exits.pop()
 
244
            try:
 
245
                if exit(*exc):
 
246
                    exc = (None, None, None)
 
247
            except:
 
248
                exc = sys.exc_info()
 
249
        if exc != (None, None, None):
 
250
            # Don't rely on sys.exc_info() still containing
 
251
            # the right information. Another exception may
 
252
            # have been raised and caught by an exit method
 
253
            raise exc
 
254