Skip to content

Commit df51e84

Browse files
committed
session: provide a way for library user to properly cleanup the event loop
Also close the loop automatically on connection error.
1 parent 320cb7d commit df51e84

File tree

8 files changed

+61
-3
lines changed

8 files changed

+61
-3
lines changed

neovim/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ def attach(session_type, address=None, port=None,
9696
nvim = attach('socket', path=<path>)
9797
nvim = attach('child', argv=<argv>)
9898
nvim = attach('stdio')
99+
100+
When the session is not needed anymore, it is recommended to explicitly
101+
close it:
102+
nvim.close()
103+
It is also possible to use the session as a context mangager:
104+
with attach('socket', path=thepath) as nvim:
105+
print(nvim.funcs.getpid())
106+
print(nvim.current.line)
107+
This will automatically close the session when you're done with it, or
108+
when an error occured.
109+
110+
99111
"""
100112
session = (tcp_session(address, port) if session_type == 'tcp' else
101113
socket_session(path) if session_type == 'socket' else

neovim/api/nvim.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Main Nvim interface."""
2-
import functools
32
import os
43
import sys
4+
from functools import partial
55
from traceback import format_stack
66

77
from msgpack import ExtType
@@ -180,6 +180,21 @@ def stop_loop(self):
180180
"""Stop the event loop being started with `run_loop`."""
181181
self._session.stop()
182182

183+
def close(self):
184+
"""Close the nvim session and release its resources."""
185+
self._session.close()
186+
187+
def __enter__(self):
188+
""""Enter nvim session as a context manager."""
189+
return self
190+
191+
def __exit__(self, *exc_info):
192+
""""Exict nvim session as context manager.
193+
194+
Closes the event loop.
195+
"""
196+
self.close()
197+
183198
def with_decode(self, decode=True):
184199
"""Initialize a new Nvim instance."""
185200
return Nvim(self._session, self.channel_id,
@@ -439,7 +454,7 @@ def __init__(self, nvim):
439454
self._nvim = nvim
440455

441456
def __getattr__(self, name):
442-
return functools.partial(self._nvim.call, name)
457+
return partial(self._nvim.call, name)
443458

444459

445460
class NvimError(Exception):

neovim/msgpack_rpc/async_session.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ def stop(self):
7070
"""Stop the event loop."""
7171
self._msgpack_stream.stop()
7272

73+
def close(self):
74+
"""Close the event loop."""
75+
self._msgpack_stream.close()
76+
7377
def _on_message(self, msg):
7478
try:
7579
self._handlers.get(msg[0], self._on_invalid_message)(msg)

neovim/msgpack_rpc/event_loop/asyncio.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class AsyncioEventLoop(BaseEventLoop, asyncio.Protocol,
3939
def connection_made(self, transport):
4040
"""Used to signal `asyncio.Protocol` of a successful connection."""
4141
self._transport = transport
42+
self._raw_transport = transport
4243
if isinstance(transport, asyncio.SubprocessTransport):
4344
self._transport = transport.get_pipe_transport(0)
4445

@@ -74,6 +75,7 @@ def _init(self):
7475
self._loop = loop_cls()
7576
self._queued_data = deque()
7677
self._fact = lambda: self
78+
self._raw_transport = None
7779

7880
def _connect_tcp(self, address, port):
7981
coroutine = self._loop.create_connection(self._fact, address, port)
@@ -112,6 +114,11 @@ def _run(self):
112114
def _stop(self):
113115
self._loop.stop()
114116

117+
def _close(self):
118+
if self._raw_transport is not None:
119+
self._raw_transport.close()
120+
self._loop.close()
121+
115122
def _threadsafe_call(self, fn):
116123
self._loop.call_soon_threadsafe(fn)
117124

neovim/msgpack_rpc/event_loop/base.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ def __init__(self, transport_type, *args):
8585
self._on_data = None
8686
self._error = None
8787
self._init()
88-
getattr(self, '_connect_{}'.format(transport_type))(*args)
88+
try:
89+
getattr(self, '_connect_{}'.format(transport_type))(*args)
90+
except Exception as e:
91+
self.close()
92+
raise e
8993
self._start_reading()
9094

9195
def connect_tcp(self, address, port):
@@ -148,6 +152,11 @@ def stop(self):
148152
self._stop()
149153
debug('Stopped event loop')
150154

155+
def close(self):
156+
"""Stop the event loop."""
157+
self._close()
158+
debug('Closed event loop')
159+
151160
def _on_signal(self, signum):
152161
msg = 'Received {}'.format(self._signames[signum])
153162
debug(msg)

neovim/msgpack_rpc/event_loop/uv.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ def _run(self):
9797
def _stop(self):
9898
self._loop.stop()
9999

100+
def _close(self):
101+
pass
102+
100103
def _threadsafe_call(self, fn):
101104
self._callbacks.append(fn)
102105
self._async.send()

neovim/msgpack_rpc/msgpack_stream.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ def stop(self):
4848
"""Stop the event loop."""
4949
self._event_loop.stop()
5050

51+
def close(self):
52+
"""Close the event loop."""
53+
self._event_loop.close()
54+
5155
def _on_data(self, data):
5256
self._unpacker.feed(data)
5357
while True:

neovim/msgpack_rpc/session.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ def stop(self):
140140
"""Stop the event loop."""
141141
self._async_session.stop()
142142

143+
def close(self):
144+
"""Close the event loop."""
145+
self._async_session.close()
146+
143147
def _yielding_request(self, method, args):
144148
gr = greenlet.getcurrent()
145149
parent = gr.parent

0 commit comments

Comments
 (0)