/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/smart/server.py

  • Committer: John Arbash Meinel
  • Date: 2007-05-31 20:29:04 UTC
  • mto: This revision was merged to the branch mainline in revision 2499.
  • Revision ID: john@arbash-meinel.com-20070531202904-34h7ygudo7qq9ha1
Update the code so that symlinks aren't cached at incorrect times
and fix the tests so that they don't assume files and symlinks
get cached even when the timestamp doesn't declare them 'safe'.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
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
 
 
17
"""Server for smart-server protocol."""
 
18
 
 
19
import errno
 
20
import socket
 
21
import threading
 
22
 
 
23
from bzrlib.hooks import Hooks
 
24
from bzrlib import (
 
25
    trace,
 
26
    transport,
 
27
)
 
28
from bzrlib.smart.medium import SmartServerSocketStreamMedium
 
29
 
 
30
 
 
31
class SmartTCPServer(object):
 
32
    """Listens on a TCP socket and accepts connections from smart clients.
 
33
 
 
34
    Each connection will be served by a SmartServerSocketStreamMedium running in
 
35
    a thread.
 
36
 
 
37
    hooks: An instance of SmartServerHooks.
 
38
    """
 
39
 
 
40
    def __init__(self, backing_transport, host='127.0.0.1', port=0):
 
41
        """Construct a new server.
 
42
 
 
43
        To actually start it running, call either start_background_thread or
 
44
        serve.
 
45
 
 
46
        :param host: Name of the interface to listen on.
 
47
        :param port: TCP port to listen on, or 0 to allocate a transient port.
 
48
        """
 
49
        # let connections timeout so that we get a chance to terminate
 
50
        # Keep a reference to the exceptions we want to catch because the socket
 
51
        # module's globals get set to None during interpreter shutdown.
 
52
        from socket import timeout as socket_timeout
 
53
        from socket import error as socket_error
 
54
        self._socket_error = socket_error
 
55
        self._socket_timeout = socket_timeout
 
56
        self._server_socket = socket.socket()
 
57
        self._server_socket.bind((host, port))
 
58
        self._sockname = self._server_socket.getsockname()
 
59
        self.port = self._sockname[1]
 
60
        self._server_socket.listen(1)
 
61
        self._server_socket.settimeout(1)
 
62
        self.backing_transport = backing_transport
 
63
        self._started = threading.Event()
 
64
        self._stopped = threading.Event()
 
65
 
 
66
    def serve(self):
 
67
        self._should_terminate = False
 
68
        for hook in SmartTCPServer.hooks['server_started']:
 
69
            hook(self.backing_transport.base, self.get_url())
 
70
        self._started.set()
 
71
        try:
 
72
            try:
 
73
                while not self._should_terminate:
 
74
                    try:
 
75
                        conn, client_addr = self._server_socket.accept()
 
76
                    except self._socket_timeout:
 
77
                        # just check if we're asked to stop
 
78
                        pass
 
79
                    except self._socket_error, e:
 
80
                        # if the socket is closed by stop_background_thread
 
81
                        # we might get a EBADF here, any other socket errors
 
82
                        # should get logged.
 
83
                        if e.args[0] != errno.EBADF:
 
84
                            trace.warning("listening socket error: %s", e)
 
85
                    else:
 
86
                        self.serve_conn(conn)
 
87
            except KeyboardInterrupt:
 
88
                # dont log when CTRL-C'd.
 
89
                raise
 
90
            except Exception, e:
 
91
                trace.error("Unhandled smart server error.")
 
92
                trace.log_exception_quietly()
 
93
                raise
 
94
        finally:
 
95
            self._stopped.set()
 
96
            try:
 
97
                # ensure the server socket is closed.
 
98
                self._server_socket.close()
 
99
            except self._socket_error:
 
100
                # ignore errors on close
 
101
                pass
 
102
            for hook in SmartTCPServer.hooks['server_stopped']:
 
103
                hook(self.backing_transport.base, self.get_url())
 
104
 
 
105
    def get_url(self):
 
106
        """Return the url of the server"""
 
107
        return "bzr://%s:%d/" % self._sockname
 
108
 
 
109
    def serve_conn(self, conn):
 
110
        # For WIN32, where the timeout value from the listening socket
 
111
        # propogates to the newly accepted socket.
 
112
        conn.setblocking(True)
 
113
        conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 
114
        handler = SmartServerSocketStreamMedium(conn, self.backing_transport)
 
115
        connection_thread = threading.Thread(None, handler.serve, name='smart-server-child')
 
116
        connection_thread.setDaemon(True)
 
117
        connection_thread.start()
 
118
 
 
119
    def start_background_thread(self):
 
120
        self._started.clear()
 
121
        self._server_thread = threading.Thread(None,
 
122
                self.serve,
 
123
                name='server-' + self.get_url())
 
124
        self._server_thread.setDaemon(True)
 
125
        self._server_thread.start()
 
126
        self._started.wait()
 
127
 
 
128
    def stop_background_thread(self):
 
129
        self._stopped.clear()
 
130
        # tell the main loop to quit on the next iteration.
 
131
        self._should_terminate = True
 
132
        # close the socket - gives error to connections from here on in,
 
133
        # rather than a connection reset error to connections made during
 
134
        # the period between setting _should_terminate = True and 
 
135
        # the current request completing/aborting. It may also break out the
 
136
        # main loop if it was currently in accept() (on some platforms).
 
137
        try:
 
138
            self._server_socket.close()
 
139
        except self._socket_error:
 
140
            # ignore errors on close
 
141
            pass
 
142
        if not self._stopped.isSet():
 
143
            # server has not stopped (though it may be stopping)
 
144
            # its likely in accept(), so give it a connection
 
145
            temp_socket = socket.socket()
 
146
            temp_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 
147
            if not temp_socket.connect_ex(self._sockname):
 
148
                # and close it immediately: we dont choose to send any requests.
 
149
                temp_socket.close()
 
150
        self._stopped.wait()
 
151
        self._server_thread.join()
 
152
 
 
153
 
 
154
class SmartServerHooks(Hooks):
 
155
    """Hooks for the smart server."""
 
156
 
 
157
    def __init__(self):
 
158
        """Create the default hooks.
 
159
 
 
160
        These are all empty initially, because by default nothing should get
 
161
        notified.
 
162
        """
 
163
        Hooks.__init__(self)
 
164
        # Introduced in 0.16:
 
165
        # invoked whenever the server starts serving a directory.
 
166
        # The api signature is (backing url, public url).
 
167
        self['server_started'] = []
 
168
        # Introduced in 0.16:
 
169
        # invoked whenever the server stops serving a directory.
 
170
        # The api signature is (backing url, public url).
 
171
        self['server_stopped'] = []
 
172
 
 
173
SmartTCPServer.hooks = SmartServerHooks()
 
174
 
 
175
 
 
176
class SmartTCPServer_for_testing(SmartTCPServer):
 
177
    """Server suitable for use by transport tests.
 
178
    
 
179
    This server is backed by the process's cwd.
 
180
    """
 
181
 
 
182
    def __init__(self):
 
183
        SmartTCPServer.__init__(self, None)
 
184
        
 
185
    def get_backing_transport(self, backing_transport_server):
 
186
        """Get a backing transport from a server we are decorating."""
 
187
        return transport.get_transport(backing_transport_server.get_url())
 
188
 
 
189
    def setUp(self, backing_transport_server=None):
 
190
        """Set up server for testing"""
 
191
        from bzrlib.transport.chroot import ChrootServer
 
192
        if backing_transport_server is None:
 
193
            from bzrlib.transport.local import LocalURLServer
 
194
            backing_transport_server = LocalURLServer()
 
195
        self.chroot_server = ChrootServer(
 
196
            self.get_backing_transport(backing_transport_server))
 
197
        self.chroot_server.setUp()
 
198
        self.backing_transport = transport.get_transport(
 
199
            self.chroot_server.get_url())
 
200
        self.start_background_thread()
 
201
 
 
202
    def tearDown(self):
 
203
        self.stop_background_thread()
 
204
        self.chroot_server.tearDown()
 
205
 
 
206
    def get_bogus_url(self):
 
207
        """Return a URL which will fail to connect"""
 
208
        return 'bzr://127.0.0.1:1/'
 
209
 
 
210
 
 
211
class ReadonlySmartTCPServer_for_testing(SmartTCPServer_for_testing):
 
212
    """Get a readonly server for testing."""
 
213
 
 
214
    def get_backing_transport(self, backing_transport_server):
 
215
        """Get a backing transport from a server we are decorating."""
 
216
        url = 'readonly+' + backing_transport_server.get_url()
 
217
        return transport.get_transport(url)
 
218