mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Use a dedicated socket pair to control the server
Allows for a faster wakeup() implementation and the server can receive arbitrary control messages
This commit is contained in:
parent
6764873483
commit
97d32ecdf6
@ -15,12 +15,14 @@ from calibre.ptempfile import TemporaryDirectory
|
|||||||
from calibre.srv.opts import Options
|
from calibre.srv.opts import Options
|
||||||
from calibre.srv.utils import (
|
from calibre.srv.utils import (
|
||||||
socket_errors_socket_closed, socket_errors_nonblocking, HandleInterrupt,
|
socket_errors_socket_closed, socket_errors_nonblocking, HandleInterrupt,
|
||||||
socket_errors_eintr, start_cork, stop_cork, DESIRED_SEND_BUFFER_SIZE)
|
socket_errors_eintr, start_cork, stop_cork, DESIRED_SEND_BUFFER_SIZE,
|
||||||
|
create_sock_pair)
|
||||||
from calibre.utils.socket_inheritance import set_socket_inherit
|
from calibre.utils.socket_inheritance import set_socket_inherit
|
||||||
from calibre.utils.logging import ThreadSafeLog
|
from calibre.utils.logging import ThreadSafeLog
|
||||||
from calibre.utils.monotonic import monotonic
|
from calibre.utils.monotonic import monotonic
|
||||||
|
|
||||||
READ, WRITE, RDWR = 'READ', 'WRITE', 'RDWR'
|
READ, WRITE, RDWR = 'READ', 'WRITE', 'RDWR'
|
||||||
|
WAKEUP, JOB_DONE = bytes(bytearray(xrange(2)))
|
||||||
|
|
||||||
class ReadBuffer(object): # {{{
|
class ReadBuffer(object): # {{{
|
||||||
|
|
||||||
@ -109,7 +111,7 @@ class ReadBuffer(object): # {{{
|
|||||||
return ans
|
return ans
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class Connection(object):
|
class Connection(object): # {{{
|
||||||
|
|
||||||
def __init__(self, socket, opts, ssl_context, tdir, addr):
|
def __init__(self, socket, opts, ssl_context, tdir, addr):
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
@ -244,6 +246,7 @@ class Connection(object):
|
|||||||
|
|
||||||
def handle_timeout(self):
|
def handle_timeout(self):
|
||||||
return False
|
return False
|
||||||
|
# }}}
|
||||||
|
|
||||||
class ServerLoop(object):
|
class ServerLoop(object):
|
||||||
|
|
||||||
@ -282,6 +285,11 @@ class ServerLoop(object):
|
|||||||
set_socket_inherit(self.pre_activated_socket, False)
|
set_socket_inherit(self.pre_activated_socket, False)
|
||||||
self.bind_address = self.pre_activated_socket.getsockname()
|
self.bind_address = self.pre_activated_socket.getsockname()
|
||||||
|
|
||||||
|
self.create_control_connection()
|
||||||
|
|
||||||
|
def create_control_connection(self):
|
||||||
|
self.control_in, self.control_out = create_sock_pair()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s(%r)" % (self.__class__.__name__, self.bind_address)
|
return "%s(%r)" % (self.__class__.__name__, self.bind_address)
|
||||||
__repr__ = __str__
|
__repr__ = __str__
|
||||||
@ -399,7 +407,7 @@ class ServerLoop(object):
|
|||||||
writable = []
|
writable = []
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
readable, writable, _ = select.select([self.socket.fileno()] + read_needed, write_needed, [], self.opts.timeout)
|
readable, writable, _ = select.select([self.socket.fileno(), self.control_out.fileno()] + read_needed, write_needed, [], self.opts.timeout)
|
||||||
except ValueError: # self.socket.fileno() == -1
|
except ValueError: # self.socket.fileno() == -1
|
||||||
self.ready = False
|
self.ready = False
|
||||||
self.log.error('Listening socket was unexpectedly terminated')
|
self.log.error('Listening socket was unexpectedly terminated')
|
||||||
@ -442,28 +450,7 @@ class ServerLoop(object):
|
|||||||
self.close(s, conn)
|
self.close(s, conn)
|
||||||
|
|
||||||
def wakeup(self):
|
def wakeup(self):
|
||||||
# Touch our own socket to make select() return immediately.
|
self.control_in.sendall(WAKEUP)
|
||||||
sock = getattr(self, "socket", None)
|
|
||||||
if sock is not None:
|
|
||||||
try:
|
|
||||||
host, port = sock.getsockname()[:2]
|
|
||||||
except socket.error as e:
|
|
||||||
if e.errno not in socket_errors_socket_closed:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
|
|
||||||
socket.SOCK_STREAM):
|
|
||||||
af, socktype, proto, canonname, sa = res
|
|
||||||
s = None
|
|
||||||
try:
|
|
||||||
s = socket.socket(af, socktype, proto)
|
|
||||||
s.settimeout(1.0)
|
|
||||||
s.connect((host, port))
|
|
||||||
s.close()
|
|
||||||
except socket.error:
|
|
||||||
if s is not None:
|
|
||||||
s.close()
|
|
||||||
return sock
|
|
||||||
|
|
||||||
def close(self, s, conn):
|
def close(self, s, conn):
|
||||||
self.connection_map.pop(s, None)
|
self.connection_map.pop(s, None)
|
||||||
@ -471,6 +458,7 @@ class ServerLoop(object):
|
|||||||
|
|
||||||
def get_actions(self, readable, writable):
|
def get_actions(self, readable, writable):
|
||||||
listener = self.socket.fileno()
|
listener = self.socket.fileno()
|
||||||
|
control = self.control_out.fileno()
|
||||||
for s in readable:
|
for s in readable:
|
||||||
if s == listener:
|
if s == listener:
|
||||||
sock, addr = self.accept()
|
sock, addr = self.accept()
|
||||||
@ -480,6 +468,24 @@ class ServerLoop(object):
|
|||||||
self.connection_map[s] = conn = self.handler(sock, self.opts, self.ssl_context, self.tdir, addr)
|
self.connection_map[s] = conn = self.handler(sock, self.opts, self.ssl_context, self.tdir, addr)
|
||||||
if self.ssl_context is not None:
|
if self.ssl_context is not None:
|
||||||
yield s, conn, RDWR
|
yield s, conn, RDWR
|
||||||
|
elif s == control:
|
||||||
|
try:
|
||||||
|
c = self.control_out.recv(1)
|
||||||
|
except socket.error:
|
||||||
|
if not self.ready:
|
||||||
|
return
|
||||||
|
self.log.error('Control socket raised an error, resetting')
|
||||||
|
self.create_control_connection()
|
||||||
|
continue
|
||||||
|
if c == JOB_DONE:
|
||||||
|
pass
|
||||||
|
elif c == WAKEUP:
|
||||||
|
pass
|
||||||
|
elif not c:
|
||||||
|
if not self.ready:
|
||||||
|
return
|
||||||
|
self.log.error('Control socket failed to recv(), resetting')
|
||||||
|
self.create_control_connection()
|
||||||
else:
|
else:
|
||||||
yield s, self.connection_map[s], READ
|
yield s, self.connection_map[s], READ
|
||||||
for s in writable:
|
for s in writable:
|
||||||
|
@ -140,7 +140,7 @@ def create_sock_pair(port=0):
|
|||||||
temp_srv_sock = socket.socket()
|
temp_srv_sock = socket.socket()
|
||||||
set_socket_inherit(temp_srv_sock, False)
|
set_socket_inherit(temp_srv_sock, False)
|
||||||
temp_srv_sock.setblocking(False)
|
temp_srv_sock.setblocking(False)
|
||||||
temp_srv_sock.bind(('localhost', port))
|
temp_srv_sock.bind(('127.0.0.1', port))
|
||||||
port = temp_srv_sock.getsockname()[1]
|
port = temp_srv_sock.getsockname()[1]
|
||||||
temp_srv_sock.listen(1)
|
temp_srv_sock.listen(1)
|
||||||
with closing(temp_srv_sock):
|
with closing(temp_srv_sock):
|
||||||
@ -148,13 +148,12 @@ def create_sock_pair(port=0):
|
|||||||
client_sock = socket.socket()
|
client_sock = socket.socket()
|
||||||
client_sock.setblocking(False)
|
client_sock.setblocking(False)
|
||||||
set_socket_inherit(client_sock, False)
|
set_socket_inherit(client_sock, False)
|
||||||
while True:
|
try:
|
||||||
try:
|
client_sock.connect(('127.0.0.1', port))
|
||||||
client_sock.connect(('localhost', port))
|
except socket.error as err:
|
||||||
except socket.error as err:
|
# EWOULDBLOCK is not an error, as the socket is non-blocking
|
||||||
# EWOULDBLOCK is not an error, as the socket is non-blocking
|
if err.errno not in socket_errors_nonblocking:
|
||||||
if err.errno not in socket_errors_nonblocking:
|
raise
|
||||||
raise
|
|
||||||
|
|
||||||
# Use select to wait for connect() to succeed.
|
# Use select to wait for connect() to succeed.
|
||||||
timeout = 1
|
timeout = 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user