~bzr-pqm/bzr/bzr.dev

2400.1.2 by Andrew Bennetts
Move SmartTCPServer classes into bzrlib/smart/server.py
1
# Copyright (C) 2006, 2007 Canonical Ltd
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
2018.5.19 by Andrew Bennetts
Add docstrings to all the new modules, and a few other places.
17
"""Server for smart-server protocol."""
18
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
19
import errno
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
20
import socket
21
import os
22
import threading
23
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
24
from bzrlib.hooks import Hooks
2018.5.21 by Andrew Bennetts
Move bzrlib.transport.smart to bzrlib.smart
25
from bzrlib.smart import medium
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
26
from bzrlib import (
2018.5.15 by Andrew Bennetts
Tidy some imports, and bugs introduced when adding server.py
27
    trace,
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
28
    transport,
29
    urlutils,
30
)
2400.1.3 by Andrew Bennetts
Split smart transport code into several separate modules.
31
from bzrlib.smart.medium import SmartServerSocketStreamMedium
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
32
33
34
class SmartTCPServer(object):
35
    """Listens on a TCP socket and accepts connections from smart clients.
2018.5.139 by Andrew Bennetts
Merge from bzr.dev, resolving conflicts.
36
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
37
    Each connection will be served by a SmartServerSocketStreamMedium running in
2018.5.139 by Andrew Bennetts
Merge from bzr.dev, resolving conflicts.
38
    a thread.
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
39
40
    hooks: An instance of SmartServerHooks.
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
41
    """
42
43
    def __init__(self, backing_transport, host='127.0.0.1', port=0):
44
        """Construct a new server.
45
46
        To actually start it running, call either start_background_thread or
47
        serve.
48
49
        :param host: Name of the interface to listen on.
50
        :param port: TCP port to listen on, or 0 to allocate a transient port.
51
        """
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
52
        # let connections timeout so that we get a chance to terminate
53
        # Keep a reference to the exceptions we want to catch because the socket
54
        # module's globals get set to None during interpreter shutdown.
55
        from socket import timeout as socket_timeout
56
        from socket import error as socket_error
57
        self._socket_error = socket_error
58
        self._socket_timeout = socket_timeout
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
59
        self._server_socket = socket.socket()
60
        self._server_socket.bind((host, port))
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
61
        self._sockname = self._server_socket.getsockname()
62
        self.port = self._sockname[1]
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
63
        self._server_socket.listen(1)
64
        self._server_socket.settimeout(1)
65
        self.backing_transport = backing_transport
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
66
        self._started = threading.Event()
67
        self._stopped = threading.Event()
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
68
69
    def serve(self):
70
        self._should_terminate = False
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
71
        for hook in SmartTCPServer.hooks['server_started']:
72
            hook(self.backing_transport.base, self.get_url())
73
        self._started.set()
74
        try:
75
            try:
76
                while not self._should_terminate:
77
                    try:
78
                        conn, client_addr = self._server_socket.accept()
79
                    except self._socket_timeout:
80
                        # just check if we're asked to stop
81
                        pass
82
                    except self._socket_error, e:
83
                        # if the socket is closed by stop_background_thread
84
                        # we might get a EBADF here, any other socket errors
85
                        # should get logged.
86
                        if e.args[0] != errno.EBADF:
87
                            trace.warning("listening socket error: %s", e)
88
                    else:
89
                        self.serve_conn(conn)
90
            except KeyboardInterrupt:
91
                # dont log when CTRL-C'd.
92
                raise
93
            except Exception, e:
94
                trace.error("Unhandled smart server error.")
95
                trace.log_exception_quietly()
96
                raise
97
        finally:
98
            self._stopped.set()
99
            try:
100
                # ensure the server socket is closed.
101
                self._server_socket.close()
102
            except self._socket_error:
103
                # ignore errors on close
104
                pass
105
            for hook in SmartTCPServer.hooks['server_stopped']:
106
                hook(self.backing_transport.base, self.get_url())
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
107
108
    def get_url(self):
109
        """Return the url of the server"""
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
110
        return "bzr://%s:%d/" % self._sockname
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
111
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
112
    def serve_conn(self, conn):
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
113
        # For WIN32, where the timeout value from the listening socket
114
        # propogates to the newly accepted socket.
115
        conn.setblocking(True)
116
        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
2400.1.2 by Andrew Bennetts
Move SmartTCPServer classes into bzrlib/smart/server.py
117
        handler = SmartServerSocketStreamMedium(conn, self.backing_transport)
118
        connection_thread = threading.Thread(None, handler.serve, name='smart-server-child')
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
119
        connection_thread.setDaemon(True)
120
        connection_thread.start()
121
122
    def start_background_thread(self):
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
123
        self._started.clear()
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
124
        self._server_thread = threading.Thread(None,
125
                self.serve,
126
                name='server-' + self.get_url())
127
        self._server_thread.setDaemon(True)
128
        self._server_thread.start()
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
129
        self._started.wait()
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
130
131
    def stop_background_thread(self):
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
132
        self._stopped.clear()
133
        # tell the main loop to quit on the next iteration.
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
134
        self._should_terminate = True
2400.1.7 by Andrew Bennetts
Merge from bzr.dev.
135
        # close the socket - gives error to connections from here on in,
136
        # rather than a connection reset error to connections made during
137
        # the period between setting _should_terminate = True and 
138
        # the current request completing/aborting. It may also break out the
139
        # main loop if it was currently in accept() (on some platforms).
140
        try:
141
            self._server_socket.close()
142
        except self._socket_error:
143
            # ignore errors on close
144
            pass
145
        if not self._stopped.isSet():
146
            # server has not stopped (though it may be stopping)
147
            # its likely in accept(), so give it a connection
148
            temp_socket = socket.socket()
149
            temp_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
150
            if not temp_socket.connect_ex(self._sockname):
151
                # and close it immediately: we dont choose to send any requests.
152
                temp_socket.close()
153
        self._stopped.wait()
154
        self._server_thread.join()
155
156
157
class SmartServerHooks(Hooks):
158
    """Hooks for the smart server."""
159
160
    def __init__(self):
161
        """Create the default hooks.
162
163
        These are all empty initially, because by default nothing should get
164
        notified.
165
        """
166
        Hooks.__init__(self)
167
        # Introduced in 0.16:
168
        # invoked whenever the server starts serving a directory.
169
        # The api signature is (backing url, public url).
170
        self['server_started'] = []
171
        # Introduced in 0.16:
172
        # invoked whenever the server stops serving a directory.
173
        # The api signature is (backing url, public url).
174
        self['server_stopped'] = []
175
176
SmartTCPServer.hooks = SmartServerHooks()
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
177
2400.1.2 by Andrew Bennetts
Move SmartTCPServer classes into bzrlib/smart/server.py
178
179
class SmartTCPServer_for_testing(SmartTCPServer):
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
180
    """Server suitable for use by transport tests.
181
    
2400.1.2 by Andrew Bennetts
Move SmartTCPServer classes into bzrlib/smart/server.py
182
    This server is backed by the process's cwd.
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
183
    """
184
2400.1.2 by Andrew Bennetts
Move SmartTCPServer classes into bzrlib/smart/server.py
185
    def __init__(self):
2018.5.42 by Robert Collins
Various hopefully improvements, but wsgi is broken, handing over to spiv :).
186
        SmartTCPServer.__init__(self, None)
2400.1.2 by Andrew Bennetts
Move SmartTCPServer classes into bzrlib/smart/server.py
187
        
188
    def get_backing_transport(self, backing_transport_server):
189
        """Get a backing transport from a server we are decorating."""
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
190
        return transport.get_transport(backing_transport_server.get_url())
2400.1.2 by Andrew Bennetts
Move SmartTCPServer classes into bzrlib/smart/server.py
191
2018.5.47 by Andrew Bennetts
Make SmartTCPServer_for_Testing.setUp's backing_transport_server argument optional.
192
    def setUp(self, backing_transport_server=None):
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
193
        """Set up server for testing"""
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
194
        from bzrlib.transport.chroot import ChrootServer
2018.5.47 by Andrew Bennetts
Make SmartTCPServer_for_Testing.setUp's backing_transport_server argument optional.
195
        if backing_transport_server is None:
196
            from bzrlib.transport.local import LocalURLServer
197
            backing_transport_server = LocalURLServer()
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
198
        self.chroot_server = ChrootServer(
199
            self.get_backing_transport(backing_transport_server))
200
        self.chroot_server.setUp()
201
        self.backing_transport = transport.get_transport(
202
            self.chroot_server.get_url())
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
203
        self.start_background_thread()
204
205
    def tearDown(self):
206
        self.stop_background_thread()
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
207
        self.chroot_server.tearDown()
2018.5.14 by Andrew Bennetts
Move SmartTCPServer to smart/server.py, and SmartServerRequestHandler to smart/request.py.
208
209
    def get_bogus_url(self):
210
        """Return a URL which will fail to connect"""
211
        return 'bzr://127.0.0.1:1/'
212
213
2018.5.42 by Robert Collins
Various hopefully improvements, but wsgi is broken, handing over to spiv :).
214
class ReadonlySmartTCPServer_for_testing(SmartTCPServer_for_testing):
215
    """Get a readonly server for testing."""
216
217
    def get_backing_transport(self, backing_transport_server):
218
        """Get a backing transport from a server we are decorating."""
2018.5.104 by Andrew Bennetts
Completely rework chrooted transports.
219
        url = 'readonly+' + backing_transport_server.get_url()
2018.5.42 by Robert Collins
Various hopefully improvements, but wsgi is broken, handing over to spiv :).
220
        return transport.get_transport(url)
2018.5.95 by Andrew Bennetts
Add a Transport.is_readonly remote call, let {Branch,Repository}.lock_write remote call return UnlockableTransport, and miscellaneous test fixes.
221