/
bsdsocket.py
183 lines (151 loc) · 5.78 KB
/
bsdsocket.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""BSD socket interface communication utilities"""
# Be extra careful here. The interface is used to communicate with subprocesses
# by redirecting output streams through a socket. Any exception in this module
# and failure to read out buffers will most likely lock up Spyder.
import os
import pickle
import socket
import struct
import threading
import errno
import traceback
# Local imports
from spyder.config.base import get_debug_level, STDERR
DEBUG_EDITOR = get_debug_level() >= 3
PICKLE_HIGHEST_PROTOCOL = 2
def temp_fail_retry(error, fun, *args):
"""Retry to execute function, ignoring EINTR error (interruptions)"""
while 1:
try:
return fun(*args)
except error as e:
eintr = errno.WSAEINTR if os.name == 'nt' else errno.EINTR
if e.args[0] == eintr:
continue
raise
SZ = struct.calcsize("l")
def write_packet(sock, data, already_pickled=False):
"""Write *data* to socket *sock*"""
if already_pickled:
sent_data = data
else:
sent_data = pickle.dumps(data, PICKLE_HIGHEST_PROTOCOL)
sent_data = struct.pack("l", len(sent_data)) + sent_data
nsend = len(sent_data)
while nsend > 0:
nsend -= temp_fail_retry(socket.error, sock.send, sent_data)
def read_packet(sock, timeout=None):
"""
Read data from socket *sock*
Returns None if something went wrong
"""
sock.settimeout(timeout)
dlen, data = None, None
try:
if os.name == 'nt':
# Windows implementation
datalen = sock.recv(SZ)
dlen, = struct.unpack("l", datalen)
data = b''
while len(data) < dlen:
data += sock.recv(dlen)
else:
# Linux/MacOSX implementation
# Thanks to eborisch:
# See spyder-ide/spyder#1106.
datalen = temp_fail_retry(socket.error, sock.recv,
SZ, socket.MSG_WAITALL)
if len(datalen) == SZ:
dlen, = struct.unpack("l", datalen)
data = temp_fail_retry(socket.error, sock.recv,
dlen, socket.MSG_WAITALL)
except socket.timeout:
raise
except socket.error:
data = None
finally:
sock.settimeout(None)
if data is not None:
try:
return pickle.loads(data)
except Exception:
# Catch all exceptions to avoid locking spyder
if DEBUG_EDITOR:
traceback.print_exc(file=STDERR)
return
# Using a lock object to avoid communication issues described in
# spyder-ide/spyder#857.
COMMUNICATE_LOCK = threading.Lock()
# * Old com implementation *
# See solution (1) in spyder-ide/spyder#434, comment 13:
def communicate(sock, command, settings=[]):
"""Communicate with monitor"""
try:
COMMUNICATE_LOCK.acquire()
write_packet(sock, command)
for option in settings:
write_packet(sock, option)
return read_packet(sock)
finally:
COMMUNICATE_LOCK.release()
# new com implementation:
# See solution (2) in spyder-ide/spyder#434, comment 13:
#def communicate(sock, command, settings=[], timeout=None):
# """Communicate with monitor"""
# write_packet(sock, command)
# for option in settings:
# write_packet(sock, option)
# if timeout == 0.:
# # non blocking socket is not really supported:
# # setting timeout to 0. here is equivalent (in current monitor's
# # implementation) to say 'I don't need to receive anything in return'
# return
# while True:
# output = read_packet(sock, timeout=timeout)
# if output is None:
# return
# output_command, output_data = output
# if command == output_command:
# return output_data
# elif DEBUG:
# logging.debug("###### communicate/warning /Begin ######")
# logging.debug("was expecting '%s', received '%s'" \
# % (command, output_command))
# logging.debug("###### communicate/warning /End ######")
class PacketNotReceived(object):
pass
PACKET_NOT_RECEIVED = PacketNotReceived()
if __name__ == '__main__':
if not os.name == 'nt':
# socket read/write testing - client and server in one thread
# (techtonik): the stuff below is placed into public domain
print("-- Testing standard Python socket interface --") # spyder: test-skip
address = ("127.0.0.1", 9999)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind( address )
server.listen(2)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect( address )
client.send("data to be catched".encode('utf-8'))
# accepted server socket is the one we can read from
# note that it is different from server socket
accsock, addr = server.accept()
print('..got "%s" from %s' % (accsock.recv(4096), addr)) # spyder: test-skip
# accsock.close()
# client.send("more data for recv")
#socket.error: [Errno 9] Bad file descriptor
# accsock, addr = server.accept()
#socket.error: [Errno 11] Resource temporarily unavailable
print("-- Testing BSD socket write_packet/read_packet --") # spyder: test-skip
write_packet(client, "a tiny piece of data")
print('..got "%s" from read_packet()' % (read_packet(accsock))) # spyder: test-skip
client.close()
server.close()
print("-- Done.") # spyder: test-skip