Merge lp:~cmiller/desktopcouch/getport-at-call-time into lp:desktopcouch
- getport-at-call-time
- Merge into trunk
Proposed by
Chad Miller
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~cmiller/desktopcouch/getport-at-call-time | ||||
Merge into: | lp:desktopcouch | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp:~cmiller/desktopcouch/getport-at-call-time | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu One hackers | Pending | ||
Review via email: mp+10104@code.launchpad.net |
This proposal has been superseded by a proposal from 2009-08-13.
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'desktopcouch/__init__.py' |
2 | --- desktopcouch/__init__.py 2009-08-04 11:15:52 +0000 |
3 | +++ desktopcouch/__init__.py 2009-08-13 12:12:22 +0000 |
4 | @@ -21,52 +21,66 @@ |
5 | import errno |
6 | import time |
7 | |
8 | -def find_pid(): |
9 | +def find_pid(start_if_not_running=True): |
10 | # Work out whether CouchDB is running by looking at its pid file |
11 | - from desktopcouch import local_files |
12 | - pid = '' |
13 | - try: |
14 | - fp = open(local_files.FILE_PID) |
15 | - pid = int(fp.read()) |
16 | - fp.close() |
17 | - except IOError: |
18 | - pass |
19 | + def get_pid(): |
20 | + from desktopcouch import local_files |
21 | + try: |
22 | + pid_file = local_files.FILE_PID |
23 | + with open(pid_file) as fp: |
24 | + try: |
25 | + return int(fp.read()) |
26 | + except ValueError: |
27 | + return None |
28 | + except IOError: |
29 | + return None |
30 | |
31 | - if not is_couchdb(pid): |
32 | + pid = get_pid() |
33 | + if not process_is_couchdb(pid) and start_if_not_running: |
34 | # pidfile is stale |
35 | # start CouchDB by running the startup script |
36 | - print "Desktop CouchDB is not running; starting it." |
37 | + print "Desktop CouchDB is not running; starting it.", |
38 | from desktopcouch import start_local_couchdb |
39 | start_local_couchdb.start_couchdb() |
40 | - time.sleep(2) # give the process a chance to start |
41 | + for timeout in xrange(1000): |
42 | + pid = get_pid() |
43 | + if process_is_couchdb(pid): |
44 | + break |
45 | + print ".", |
46 | + time.sleep(0.1) |
47 | |
48 | - # get the pid |
49 | - try: |
50 | - with open(local_files.FILE_PID) as pid_file: |
51 | - pid = int(pid_file.read().strip()) |
52 | - except IOError, e: |
53 | - if e.errno == ENOENT: |
54 | + if process_is_couchdb(pid): |
55 | + print " done." |
56 | + else: |
57 | + print " failed." |
58 | raise RuntimeError("desktop-couch not started") |
59 | - else: |
60 | - raise |
61 | + |
62 | return pid |
63 | |
64 | -def is_couchdb(pid): |
65 | +def process_is_couchdb__linux(pid): |
66 | + if not isinstance(pid, int): |
67 | + return False |
68 | + |
69 | proc_dir = "/proc/%s" % (pid,) |
70 | |
71 | - # check to make sure that the process still exists |
72 | - if not os.path.isdir(proc_dir): |
73 | - return False |
74 | - |
75 | - # check to make sure it is actually a desktop-couch instance |
76 | - with open(os.path.join(proc_dir, 'cmdline')) as cmd_file: |
77 | - cmd = cmd_file.read() |
78 | - if re.search('desktop-couch', cmd) is None: |
79 | + try: |
80 | + # check to make sure it is actually a desktop-couch instance |
81 | + with open(os.path.join(proc_dir, 'cmdline')) as cmd_file: |
82 | + cmd = cmd_file.read() |
83 | + if '/desktop-couch' not in cmd: |
84 | + return False |
85 | + |
86 | + # make sure it's our process. |
87 | + if not os.access(os.path.join(proc_dir, "mem"), os.W_OK): |
88 | + return False |
89 | + |
90 | + except IOError: |
91 | return False |
92 | |
93 | return True |
94 | |
95 | -def find_port(pid): |
96 | + |
97 | +def find_port__linux(pid): |
98 | # Look in the CouchDB log to find the port number, someday. |
99 | # Currently, we have to grovel around in /proc instead. |
100 | # Oh, the huge manatee... (this replaced an lsof shell recipe |
101 | @@ -76,8 +90,12 @@ |
102 | |
103 | # enumerate the process' file descriptors |
104 | fd_dir = os.path.join(proc_dir, 'fd') |
105 | - fd_paths = [os.readlink(os.path.join(fd_dir, fd)) |
106 | - for fd in os.listdir(fd_dir)] |
107 | + try: |
108 | + fd_paths = [os.readlink(os.path.join(fd_dir, fd)) |
109 | + for fd in os.listdir(fd_dir)] |
110 | + except OSError: |
111 | + raise RuntimeError("Unable to find file descriptors in /proc") |
112 | + |
113 | |
114 | # identify socket fds |
115 | socket_matches = [re.match('socket:\\[([0-9]+)\\]', p) for p in fd_paths] |
116 | @@ -108,9 +126,24 @@ |
117 | for line in tcp_file: |
118 | match = listening_regexp.match(line) |
119 | if match is not None: |
120 | - port = int(match.group(1), 16) |
121 | + port = str(int(match.group(1), 16)) |
122 | break |
123 | if port is None: |
124 | raise RuntimeError("Unable to find listening port") |
125 | |
126 | - return str(port) |
127 | + return port |
128 | + |
129 | + |
130 | + |
131 | +import platform |
132 | +os_name = platform.system() |
133 | +try: |
134 | + process_is_couchdb = { |
135 | + "Linux":process_is_couchdb__linux |
136 | + } [os_name] |
137 | + |
138 | + find_port = { |
139 | + "Linux":find_port__linux |
140 | + } [os_name] |
141 | +except KeyError: |
142 | + raise NotImplementedError("os %r is not yet supported" % (os_name,)) |
143 | |
144 | === modified file 'desktopcouch/records/tests/test_field_registry.py' |
145 | --- desktopcouch/records/tests/test_field_registry.py 2009-07-08 17:48:11 +0000 |
146 | +++ desktopcouch/records/tests/test_field_registry.py 2009-08-13 16:06:20 +0000 |
147 | @@ -18,7 +18,7 @@ |
148 | """Test cases for field mapping""" |
149 | |
150 | import copy |
151 | -from twisted.trial.unittest import TestCase as TwistedTestCase |
152 | +from testtools import TestCase |
153 | from desktopcouch.records.field_registry import ( |
154 | SimpleFieldMapping, MergeableListFieldMapping, Transformer) |
155 | from desktopcouch.records.record import Record |
156 | @@ -53,7 +53,7 @@ |
157 | super(AppTransformer, self).__init__('Test App', field_registry) |
158 | |
159 | |
160 | -class TestFieldMapping(TwistedTestCase): |
161 | +class TestFieldMapping(TestCase): |
162 | """Test Case for FieldMapping objects.""" |
163 | |
164 | def setUp(self): |
165 | @@ -85,7 +85,7 @@ |
166 | self.assertEqual(None, mapping.getValue(record)) |
167 | |
168 | |
169 | -class TestTransformer(TwistedTestCase): |
170 | +class TestTransformer(TestCase): |
171 | """Test application specific transformer classes""" |
172 | |
173 | def setUp(self): |
174 | |
175 | === modified file 'desktopcouch/records/tests/test_record.py' |
176 | --- desktopcouch/records/tests/test_record.py 2009-07-08 17:48:11 +0000 |
177 | +++ desktopcouch/records/tests/test_record.py 2009-08-13 16:06:20 +0000 |
178 | @@ -18,7 +18,7 @@ |
179 | |
180 | """Tests for the RecordDict object on which the Contacts API is built.""" |
181 | |
182 | -from twisted.trial.unittest import TestCase as TwistedTestCase |
183 | +from testtools import TestCase |
184 | |
185 | # pylint does not like relative imports from containing packages |
186 | # pylint: disable-msg=F0401 |
187 | @@ -26,7 +26,7 @@ |
188 | record_factory, IllegalKeyException, validate) |
189 | |
190 | |
191 | -class TestRecords(TwistedTestCase): |
192 | +class TestRecords(TestCase): |
193 | """Test the record functionality""" |
194 | |
195 | def setUp(self): |
196 | @@ -180,7 +180,7 @@ |
197 | self.record.record_type) |
198 | |
199 | |
200 | -class TestRecordFactory(TwistedTestCase): |
201 | +class TestRecordFactory(TestCase): |
202 | """Test Record/Mergeable List factories.""" |
203 | |
204 | def setUp(self): |
205 | |
206 | === modified file 'desktopcouch/records/tests/test_server.py' |
207 | --- desktopcouch/records/tests/test_server.py 2009-08-10 21:32:52 +0000 |
208 | +++ desktopcouch/records/tests/test_server.py 2009-08-12 14:26:40 +0000 |
209 | @@ -19,7 +19,8 @@ |
210 | """testing database/contact.py module""" |
211 | |
212 | import testtools |
213 | - |
214 | +import random |
215 | +from desktopcouch.stop_local_couchdb import stop_couchdb |
216 | from desktopcouch.records.server import CouchDatabase |
217 | from desktopcouch.records.record import Record |
218 | |
219 | @@ -49,6 +50,9 @@ |
220 | def tearDown(self): |
221 | """tear down each test""" |
222 | del self.database._server[self.dbname] |
223 | + if random.choice([1,2,3,4]) == 3: # don't harass it unnecessarily |
224 | + print u"\u2620", # death |
225 | + stop_couchdb() |
226 | |
227 | def test_get_records_by_record_type_save_view(self): |
228 | """Test getting mutliple records by type""" |
229 | |
230 | === modified file 'desktopcouch/start_local_couchdb.py' (properties changed: -x to +x) |
231 | --- desktopcouch/start_local_couchdb.py 2009-07-27 18:16:19 +0000 |
232 | +++ desktopcouch/start_local_couchdb.py 2009-08-13 12:14:05 +0000 |
233 | @@ -32,10 +32,12 @@ |
234 | """ |
235 | |
236 | from __future__ import with_statement |
237 | -import os, subprocess, sys |
238 | +import os, sys |
239 | +import subprocess |
240 | import desktopcouch |
241 | from desktopcouch import local_files |
242 | import xdg.BaseDirectory |
243 | +import errno |
244 | import time |
245 | |
246 | def dump_ini(data, filename): |
247 | @@ -52,8 +54,6 @@ |
248 | fd.write("\n") |
249 | fd.close() |
250 | |
251 | - |
252 | - |
253 | def create_ini_file(): |
254 | """Write CouchDB ini file if not already present""" |
255 | # FIXME add update trigger folder |
256 | @@ -89,7 +89,16 @@ |
257 | """Actually start the CouchDB process""" |
258 | local_exec = local_files.COUCH_EXEC_COMMAND + ['-b'] |
259 | try: |
260 | - retcode = subprocess.call(local_exec, shell=False) |
261 | + # subprocess is buggy. Chad patched, but that takes time to propagate. |
262 | + proc = subprocess.Popen(local_exec) |
263 | + while True: |
264 | + try: |
265 | + retcode = proc.wait() |
266 | + break |
267 | + except OSError, e: |
268 | + if e.errno == errno.EINTR: |
269 | + continue |
270 | + raise |
271 | if retcode < 0: |
272 | print >> sys.stderr, "Child was terminated by signal", -retcode |
273 | elif retcode > 0: |
274 | @@ -118,15 +127,30 @@ |
275 | html = fp.read() |
276 | fp.close() |
277 | |
278 | - time.sleep(1) |
279 | - pid = desktopcouch.find_pid() |
280 | - port = desktopcouch.find_port(pid) |
281 | + port = None |
282 | + for retry in xrange(10000, 0, -1): |
283 | + pid = desktopcouch.find_pid(start_if_not_running=False) |
284 | + try: |
285 | + port = desktopcouch.find_port(pid) |
286 | + break |
287 | + except RuntimeError, e: |
288 | + if retry == 1: |
289 | + raise |
290 | + time.sleep(0.01) |
291 | + continue |
292 | |
293 | - fp = open(bookmark_file, "w") |
294 | - fp.write(html.replace("[[COUCHDB_PORT]]", port)) |
295 | - fp.close() |
296 | - print "Browse your desktop CouchDB at file://%s" % \ |
297 | - os.path.realpath(bookmark_file) |
298 | + if port is None: |
299 | + print "We couldn't find desktop-CouchDB's network port. Bookmark file not written." |
300 | + try: |
301 | + os.remove(bookmark_file) |
302 | + except OSError: |
303 | + pass |
304 | + else: |
305 | + fp = open(bookmark_file, "w") |
306 | + fp.write(html.replace("[[COUCHDB_PORT]]", port)) |
307 | + fp.close() |
308 | + print "Browse your desktop CouchDB at file://%s" % \ |
309 | + os.path.realpath(bookmark_file) |
310 | |
311 | def start_couchdb(): |
312 | """Execute each step to start a desktop CouchDB""" |
313 | @@ -135,6 +159,7 @@ |
314 | update_design_documents() |
315 | write_bookmark_file() |
316 | |
317 | + |
318 | if __name__ == "__main__": |
319 | start_couchdb() |
320 | print "Desktop CouchDB started" |
321 | |
322 | === added file 'desktopcouch/stop_local_couchdb.py' |
323 | --- desktopcouch/stop_local_couchdb.py 1970-01-01 00:00:00 +0000 |
324 | +++ desktopcouch/stop_local_couchdb.py 2009-08-12 14:26:14 +0000 |
325 | @@ -0,0 +1,49 @@ |
326 | +#!/usr/bin/python |
327 | +# Copyright 2009 Canonical Ltd. |
328 | +# |
329 | +# This file is part of desktopcouch. |
330 | +# |
331 | +# desktopcouch is free software: you can redistribute it and/or modify |
332 | +# it under the terms of the GNU Lesser General Public License version 3 |
333 | +# as published by the Free Software Foundation. |
334 | +# |
335 | +# desktopcouch is distributed in the hope that it will be useful, |
336 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
337 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
338 | +# GNU Lesser General Public License for more details. |
339 | +# |
340 | +# You should have received a copy of the GNU Lesser General Public License |
341 | +# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>. |
342 | +# |
343 | +# Author: Chad Miller <chad.miller@canonical.com> |
344 | +""" |
345 | +Stop local CouchDB server. |
346 | +""" |
347 | + |
348 | +import os |
349 | +import desktopcouch |
350 | +import time |
351 | +import signal |
352 | +import errno |
353 | + |
354 | +def stop_couchdb(): |
355 | + pid = desktopcouch.find_pid(start_if_not_running=False) |
356 | + while pid is not None: |
357 | + try: |
358 | + os.kill(pid, signal.SIGTERM) |
359 | + except OSError, e: |
360 | + if e.errno == errno.ESRCH: |
361 | + break |
362 | + raise |
363 | + |
364 | + for retry in xrange(300): |
365 | + try: |
366 | + os.kill(pid, 0) # test existence. sig-zero is special. |
367 | + except OSError: |
368 | + break |
369 | + time.sleep(0.01) |
370 | + |
371 | + pid = desktopcouch.find_pid(start_if_not_running=False) |
372 | + |
373 | +if __name__ == "__main__": |
374 | + stop_couchdb() |