~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/server.py

  • Committer: Martin Pool
  • Date: 2007-06-21 04:27:47 UTC
  • mto: This revision was merged to the branch mainline in revision 2551.
  • Revision ID: mbp@sourcefrog.net-20070621042747-e3g0tdn8if750mv5
More commit specs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
18
18
 
19
19
import errno
20
20
import socket
21
 
import sys
22
21
import threading
23
22
 
24
23
from bzrlib.hooks import Hooks
25
24
from bzrlib import (
26
 
    errors,
27
25
    trace,
28
26
    transport,
29
27
)
39
37
    hooks: An instance of SmartServerHooks.
40
38
    """
41
39
 
42
 
    def __init__(self, backing_transport, host='127.0.0.1', port=0,
43
 
                 root_client_path='/'):
 
40
    def __init__(self, backing_transport, host='127.0.0.1', port=0):
44
41
        """Construct a new server.
45
42
 
46
43
        To actually start it running, call either start_background_thread or
47
44
        serve.
48
45
 
49
 
        :param backing_transport: The transport to serve.
50
46
        :param host: Name of the interface to listen on.
51
47
        :param port: TCP port to listen on, or 0 to allocate a transient port.
52
 
        :param root_client_path: The client path that will correspond to root
53
 
            of backing_transport.
54
48
        """
55
49
        # let connections timeout so that we get a chance to terminate
56
50
        # Keep a reference to the exceptions we want to catch because the socket
60
54
        self._socket_error = socket_error
61
55
        self._socket_timeout = socket_timeout
62
56
        self._server_socket = socket.socket()
63
 
        # SO_REUSERADDR has a different meaning on Windows
64
 
        if sys.platform != 'win32':
65
 
            self._server_socket.setsockopt(socket.SOL_SOCKET,
66
 
                socket.SO_REUSEADDR, 1)
67
 
        try:
68
 
            self._server_socket.bind((host, port))
69
 
        except self._socket_error, message:
70
 
            raise errors.CannotBindAddress(host, port, message)
 
57
        self._server_socket.bind((host, port))
71
58
        self._sockname = self._server_socket.getsockname()
72
59
        self.port = self._sockname[1]
73
60
        self._server_socket.listen(1)
75
62
        self.backing_transport = backing_transport
76
63
        self._started = threading.Event()
77
64
        self._stopped = threading.Event()
78
 
        self.root_client_path = root_client_path
79
65
 
80
 
    def serve(self, thread_name_suffix=''):
 
66
    def serve(self):
81
67
        self._should_terminate = False
82
 
        # for hooks we are letting code know that a server has started (and
83
 
        # later stopped).
84
 
        # There are three interesting urls:
85
 
        # The URL the server can be contacted on. (e.g. bzr://host/)
86
 
        # The URL that a commit done on the same machine as the server will
87
 
        # have within the servers space. (e.g. file:///home/user/source)
88
 
        # The URL that will be given to other hooks in the same process -
89
 
        # the URL of the backing transport itself. (e.g. chroot+:///)
90
 
        # We need all three because:
91
 
        #  * other machines see the first
92
 
        #  * local commits on this machine should be able to be mapped to
93
 
        #    this server 
94
 
        #  * commits the server does itself need to be mapped across to this
95
 
        #    server.
96
 
        # The latter two urls are different aliases to the servers url,
97
 
        # so we group those in a list - as there might be more aliases 
98
 
        # in the future.
99
 
        backing_urls = [self.backing_transport.base]
100
 
        try:
101
 
            backing_urls.append(self.backing_transport.external_url())
102
 
        except errors.InProcessTransport:
103
 
            pass
104
68
        for hook in SmartTCPServer.hooks['server_started']:
105
 
            hook(backing_urls, self.get_url())
 
69
            hook(self.backing_transport.base, self.get_url())
106
70
        self._started.set()
107
71
        try:
108
72
            try:
119
83
                        if e.args[0] != errno.EBADF:
120
84
                            trace.warning("listening socket error: %s", e)
121
85
                    else:
122
 
                        self.serve_conn(conn, thread_name_suffix)
 
86
                        self.serve_conn(conn)
123
87
            except KeyboardInterrupt:
124
88
                # dont log when CTRL-C'd.
125
89
                raise
136
100
                # ignore errors on close
137
101
                pass
138
102
            for hook in SmartTCPServer.hooks['server_stopped']:
139
 
                hook(backing_urls, self.get_url())
 
103
                hook(self.backing_transport.base, self.get_url())
140
104
 
141
105
    def get_url(self):
142
106
        """Return the url of the server"""
143
107
        return "bzr://%s:%d/" % self._sockname
144
108
 
145
 
    def serve_conn(self, conn, thread_name_suffix):
 
109
    def serve_conn(self, conn):
146
110
        # For WIN32, where the timeout value from the listening socket
147
111
        # propogates to the newly accepted socket.
148
112
        conn.setblocking(True)
149
113
        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
150
 
        handler = SmartServerSocketStreamMedium(
151
 
            conn, self.backing_transport, self.root_client_path)
152
 
        thread_name = 'smart-server-child' + thread_name_suffix
153
 
        connection_thread = threading.Thread(
154
 
            None, handler.serve, name=thread_name)
 
114
        handler = SmartServerSocketStreamMedium(conn, self.backing_transport)
 
115
        connection_thread = threading.Thread(None, handler.serve, name='smart-server-child')
155
116
        connection_thread.setDaemon(True)
156
117
        connection_thread.start()
157
118
 
158
 
    def start_background_thread(self, thread_name_suffix=''):
 
119
    def start_background_thread(self):
159
120
        self._started.clear()
160
121
        self._server_thread = threading.Thread(None,
161
 
                self.serve, args=(thread_name_suffix,),
 
122
                self.serve,
162
123
                name='server-' + self.get_url())
163
124
        self._server_thread.setDaemon(True)
164
125
        self._server_thread.start()
202
163
        Hooks.__init__(self)
203
164
        # Introduced in 0.16:
204
165
        # invoked whenever the server starts serving a directory.
205
 
        # The api signature is (backing urls, public url).
 
166
        # The api signature is (backing url, public url).
206
167
        self['server_started'] = []
207
168
        # Introduced in 0.16:
208
169
        # invoked whenever the server stops serving a directory.
209
 
        # The api signature is (backing urls, public url).
 
170
        # The api signature is (backing url, public url).
210
171
        self['server_stopped'] = []
211
172
 
212
173
SmartTCPServer.hooks = SmartServerHooks()
218
179
    This server is backed by the process's cwd.
219
180
    """
220
181
 
221
 
    def __init__(self, thread_name_suffix=''):
 
182
    def __init__(self):
222
183
        SmartTCPServer.__init__(self, None)
223
 
        self.client_path_extra = None
224
 
        self.thread_name_suffix = thread_name_suffix
225
184
        
226
185
    def get_backing_transport(self, backing_transport_server):
227
186
        """Get a backing transport from a server we are decorating."""
228
187
        return transport.get_transport(backing_transport_server.get_url())
229
188
 
230
 
    def setUp(self, backing_transport_server=None,
231
 
              client_path_extra='/extra/'):
232
 
        """Set up server for testing.
233
 
        
234
 
        :param backing_transport_server: backing server to use.  If not
235
 
            specified, a LocalURLServer at the current working directory will
236
 
            be used.
237
 
        :param client_path_extra: a path segment starting with '/' to append to
238
 
            the root URL for this server.  For instance, a value of '/foo/bar/'
239
 
            will mean the root of the backing transport will be published at a
240
 
            URL like `bzr://127.0.0.1:nnnn/foo/bar/`, rather than
241
 
            `bzr://127.0.0.1:nnnn/`.  Default value is `extra`, so that tests
242
 
            by default will fail unless they do the necessary path translation.
243
 
        """
244
 
        if not client_path_extra.startswith('/'):
245
 
            raise ValueError(client_path_extra)
 
189
    def setUp(self, backing_transport_server=None):
 
190
        """Set up server for testing"""
246
191
        from bzrlib.transport.chroot import ChrootServer
247
192
        if backing_transport_server is None:
248
193
            from bzrlib.transport.local import LocalURLServer
252
197
        self.chroot_server.setUp()
253
198
        self.backing_transport = transport.get_transport(
254
199
            self.chroot_server.get_url())
255
 
        self.root_client_path = self.client_path_extra = client_path_extra
256
 
        self.start_background_thread(self.thread_name_suffix)
 
200
        self.start_background_thread()
257
201
 
258
202
    def tearDown(self):
259
203
        self.stop_background_thread()
260
204
        self.chroot_server.tearDown()
261
205
 
262
 
    def get_url(self):
263
 
        url = super(SmartTCPServer_for_testing, self).get_url()
264
 
        return url[:-1] + self.client_path_extra
265
 
 
266
206
    def get_bogus_url(self):
267
207
        """Return a URL which will fail to connect"""
268
208
        return 'bzr://127.0.0.1:1/'
276
216
        url = 'readonly+' + backing_transport_server.get_url()
277
217
        return transport.get_transport(url)
278
218
 
279
 
 
280
 
class SmartTCPServer_for_testing_v2_only(SmartTCPServer_for_testing):
281
 
    """A variation of SmartTCPServer_for_testing that limits the client to
282
 
    using RPCs in protocol v2 (i.e. bzr <= 1.5).
283
 
    """
284
 
 
285
 
    def get_url(self):
286
 
        url = super(SmartTCPServer_for_testing_v2_only, self).get_url()
287
 
        url = 'bzr-v2://' + url[len('bzr://'):]
288
 
        return url
289
 
 
290
 
 
291
 
class ReadonlySmartTCPServer_for_testing_v2_only(SmartTCPServer_for_testing_v2_only):
292
 
    """Get a readonly server for testing."""
293
 
 
294
 
    def get_backing_transport(self, backing_transport_server):
295
 
        """Get a backing transport from a server we are decorating."""
296
 
        url = 'readonly+' + backing_transport_server.get_url()
297
 
        return transport.get_transport(url)
298
 
 
299