~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/smart/server.py

  • Committer: Vincent Ladeuil
  • Date: 2007-06-06 13:52:02 UTC
  • mto: (2485.8.44 bzr.connection.sharing)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070606135202-mqhxcv6z57uce434
Fix merge multiple connections. Test suite *not* passing (sftp
refactoring pending but unrelated to merge).

* bzrlib/builtins.py:
(cmd_merge.run): Fix the multiple connections bug by reusing the
tramsport used to check for a bundle and keep all other used
transports in possible_transports.
(_merge_helper): Add a possible_transports parameter for
reuse.

* bzrlib/transport/__init__.py:
(Transport._reuse_for): By default, Transports are not reusable.
(ConnectedTransport._reuse_for): ConnectedTransports are reusable
under certain conditions.
(_urlRE): Fix misleading group name.
(_try_transport_factories): Moved after get_transport (another use
case for moved lines). The do_catching_redirections was
incorrectly inserted between get_transport and
_try_transport_factories.

* bzrlib/tests/test_transport.py:
(TestReusedTransports.test_reuse_same_transport)
(TestReusedTransports.test_don_t_reuse_different_transport): Add
more tests.

* bzrlib/merge.py:
(_get_tree, Merger.set_other): Add a possible_transports parameter
for reuse.

* bzrlib/bzrdir.py:
(BzrDir.open_containing): Add a possible_transports parameter for
reuse.

* bzrlib/branch.py:
(Branch.open_containing): Add a possible_transports parameter for
reuse.

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