Completed
Pull Request — master (#169)
by Björn
03:48
created

_enqueue_request_and_stop()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.2963
Metric Value
cc 1
dl 0
loc 3
ccs 1
cts 3
cp 0.3333
crap 1.2963
rs 10
1
"""Synchronous msgpack-rpc session layer."""
2 6
import logging
3 6
from collections import deque
4
5 6
from traceback import format_exc
6
7 6
import greenlet
0 ignored issues
show
Configuration introduced by
The import greenlet could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
8
9 6
logger = logging.getLogger(__name__)
10 6
error, debug, info, warn = (logger.error, logger.debug, logger.info,
11
                            logger.warning,)
12
13
14 6
class Session(object):
15
16
    """Msgpack-rpc session layer that uses coroutines for a synchronous API.
17
18
    This class provides the public msgpack-rpc API required by this library.
19
    It uses the greenlet module to handle requests and notifications coming
20
    from Nvim with a synchronous API.
21
    """
22
23 6
    def __init__(self, async_session):
24
        """Wrap `async_session` on a synchronous msgpack-rpc interface."""
25 6
        self._async_session = async_session
26 6
        self._request_cb = self._notification_cb = None
27 6
        self._pending_messages = deque()
28 6
        self._is_running = False
29 6
        self._setup_exception = None
30
31 6
    def threadsafe_call(self, fn, *args, **kwargs):
32
        """Wrapper around `AsyncSession.threadsafe_call`."""
33
34 6
        def handler():
35 6
            try:
36 6
                fn(*args, **kwargs)
37
            except Exception:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
38
                warn("error caught while excecuting async callback\n%s\n",
39
                     format_exc())
40
41 6
        def greenlet_wrapper():
42 6
            gr = greenlet.greenlet(handler)
43 6
            gr.switch()
44
45 6
        self._async_session.threadsafe_call(greenlet_wrapper)
46
47 6
    def next_message(self):
48
        """Block until a message(request or notification) is available.
49
50
        If any messages were previously enqueued, return the first in queue.
51
        If not, run the event loop until one is received.
52
        """
53 6
        if self._is_running:
54 1
            raise Exception('Event loop already running')
55 6
        if self._pending_messages:
56 6
            return self._pending_messages.popleft()
57 6
        self._async_session.run(self._enqueue_request_and_stop,
58
                                self._enqueue_notification_and_stop)
59 6
        if self._pending_messages:
60 6
            return self._pending_messages.popleft()
61
62 6
    def request(self, method, *args, **kwargs):
63
        """Send a msgpack-rpc request and block until as response is received.
64
65
        If the event loop is running, this method must have been called by a
66
        request or notification handler running on a greenlet. In that case,
67
        send the quest and yield to the parent greenlet until a response is
68
        available.
69
70
        When the event loop is not running, it will perform a blocking request
71
        like this:
72
        - Send the request
73
        - Run the loop until the response is available
74
        - Put requests/notifications received while waiting into a queue
75
76
        If the `async` flag is present and True, a asynchronous notification is
77
        sent instead. This will never block, and the return value or error is
78
        ignored.
79
        """
80 6
        async = kwargs.get('async', False)
81 6
        if async:
82 6
            self._async_session.notify(method, args)
83 6
            return
84
85 6
        if self._is_running:
86 6
            v = self._yielding_request(method, args)
87
        else:
88 6
            v = self._blocking_request(method, args)
89 6
        if not v:
90
            # EOF
91
            raise IOError('EOF')
92 6
        err, rv = v
0 ignored issues
show
Bug introduced by
The tuple unpacking with sequence defined at line 154 seems to be unbalanced; 2 value(s) for 0 label(s)

This happens when the amount of values does not equal the amount of labels:

a, b = ("a", "b", "c")  # only 2 labels for 3 values
Loading history...
93 6
        if err:
94 6
            info("'Received error: %s", err)
95 6
            raise self.error_wrapper(err)
0 ignored issues
show
Bug introduced by
The Instance of Session does not seem to have a member named error_wrapper.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
96 6
        return rv
97
98 6
    def run(self, request_cb, notification_cb, setup_cb=None):
99
        """Run the event loop to receive requests and notifications from Nvim.
100
101
        Like `AsyncSession.run()`, but `request_cb` and `notification_cb` are
102
        inside greenlets.
103
        """
104 6
        self._request_cb = request_cb
105 6
        self._notification_cb = notification_cb
106 6
        self._is_running = True
107 6
        self._setup_exception = None
108
109 6
        def on_setup():
110 6
            try:
111 6
                setup_cb()
112
            except Exception as e:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
113
                self._setup_exception = e
114
                self.stop()
115
116 6
        if setup_cb:
117
            # Create a new greenlet to handle the setup function
118 6
            gr = greenlet.greenlet(on_setup)
119 6
            gr.switch()
120
121 6
        if self._setup_exception:
122
            error('Setup error: {0}'.format(self._setup_exception))
123
            raise self._setup_exception
0 ignored issues
show
Bug introduced by
Raising NoneType while only classes or instances are allowed
Loading history...
124
125
        # Process all pending requests and notifications
126 6
        while self._pending_messages:
127
            msg = self._pending_messages.popleft()
128
            getattr(self, '_on_{0}'.format(msg[0]))(*msg[1:])
129 6
        self._async_session.run(self._on_request, self._on_notification)
130 6
        self._is_running = False
131 6
        self._request_cb = None
132 6
        self._notification_cb = None
133
134 6
        if self._setup_exception:
135
            raise self._setup_exception
0 ignored issues
show
Bug introduced by
Raising NoneType while only classes or instances are allowed
Loading history...
136
137 6
    def stop(self):
138
        """Stop the event loop."""
139 6
        self._async_session.stop()
140
141 6
    def _yielding_request(self, method, args):
142 6
        gr = greenlet.getcurrent()
143 6
        parent = gr.parent
144
145 6
        def response_cb(err, rv):
146 6
            debug('response is available for greenlet %s, switching back', gr)
147 6
            gr.switch(err, rv)
148
149 6
        self._async_session.request(method, args, response_cb)
150 6
        debug('yielding from greenlet %s to wait for response', gr)
151 6
        return parent.switch()
152
153 6
    def _blocking_request(self, method, args):
154 6
        result = []
155
156 6
        def response_cb(err, rv):
157 6
            result.extend([err, rv])
158 6
            self.stop()
159
160 6
        self._async_session.request(method, args, response_cb)
161 6
        self._async_session.run(self._enqueue_request,
162
                                self._enqueue_notification)
163 6
        return result
164
165 6
    def _enqueue_request_and_stop(self, name, args, response):
166
        self._enqueue_request(name, args, response)
167
        self.stop()
168
169 6
    def _enqueue_notification_and_stop(self, name, args):
170 6
        self._enqueue_notification(name, args)
171 6
        self.stop()
172
173 6
    def _enqueue_request(self, name, args, response):
174
        self._pending_messages.append(('request', name, args, response,))
175
176 6
    def _enqueue_notification(self, name, args):
177 6
        self._pending_messages.append(('notification', name, args,))
178
179 6
    def _on_request(self, name, args, response):
180 6
        def handler():
181 6
            try:
182 6
                rv = self._request_cb(name, args)
183 6
                debug('greenlet %s finished executing, ' +
184
                      'sending %s as response', gr, rv)
185 6
                response.send(rv)
186
            except Exception as err:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
187
                warn("error caught while processing request '%s %s': %s", name,
188
                     args, format_exc())
189
                response.send(repr(err) + "\n" + format_exc(5), error=True)
190 6
            debug('greenlet %s is now dying...', gr)
191
192
        # Create a new greenlet to handle the request
193 6
        gr = greenlet.greenlet(handler)
194 6
        debug('received rpc request, greenlet %s will handle it', gr)
195 6
        gr.switch()
196
197 6
    def _on_notification(self, name, args):
198
        def handler():
199
            try:
200
                self._notification_cb(name, args)
201
                debug('greenlet %s finished executing', gr)
202
            except Exception:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
203
                warn("error caught while processing notification '%s %s': %s",
204
                     name, args, format_exc())
205
206
207
            debug('greenlet %s is now dying...', gr)
208
209
        gr = greenlet.greenlet(handler)
210
        debug('received rpc notification, greenlet %s will handle it', gr)
211
        gr.switch()
212