Merge lp:~cmiller/desktopcouch/get_port_not_stale into lp:desktopcouch

Proposed by Chad Miller
Status: Merged
Approved by: Tim Cole
Approved revision: 51
Merged at revision: not available
Proposed branch: lp:~cmiller/desktopcouch/get_port_not_stale
Merge into: lp:desktopcouch
Diff against target: None lines
To merge this branch: bzr merge lp:~cmiller/desktopcouch/get_port_not_stale
Reviewer Review Type Date Requested Status
Tim Cole (community) Approve
Eric Casteleijn (community) Approve
Stuart Langridge (community) Abstain
Review via email: mp+10951@code.launchpad.net

Commit message

Revert the log-file reading code to the reliable (if somewhat oblique,
nonportable, and downright ugly) /proc reading code. Log files can be stale.

Change the startup code to be smarter about discovering and passing around pids
and ports, and more insistent about making sure a program is started before we
resume doing stuff that requires it to be running. This should solve races and
double execution of couchdb.

Change the regex that scans /proc networking stuff so that it doesn't balk at
non-127.0.0.1 addresses.

To post a comment you must log in.
Revision history for this message
Stuart Langridge (sil) wrote :

This code certainly works, as far as I can tell. However...I don't like the /proc approach, I like the logfile approach. It's portable and it works everywhere -- doing it the /proc way feels like a hack to me. Why not use the logfile?

review: Abstain
Revision history for this message
Tim Cole (tcole) wrote :

Is the port number available from the log file? At the time I wrote this monstrosity, it wasn't.

Revision history for this message
Eric Casteleijn (thisfred) wrote :

As this is a revert, the code looks good, and the tests pass: +1

review: Approve
Revision history for this message
Tim Cole (tcole) :
review: Approve

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-24 21:05:33 +0000
3+++ desktopcouch/__init__.py 2009-09-01 00:38:53 +0000
4@@ -16,93 +16,83 @@
5 "Desktop Couch helper files"
6
7 from __future__ import with_statement
8-import os, time
9-
10+import os, re, time
11+from desktopcouch.start_local_couchdb import process_is_couchdb, read_pidfile
12
13 def find_pid(start_if_not_running=True):
14 # Work out whether CouchDB is running by looking at its pid file
15- def get_pid():
16- from desktopcouch import local_files
17- try:
18- pid_file = local_files.FILE_PID
19- with open(pid_file) as fp:
20- try:
21- return int(fp.read())
22- except ValueError:
23- return None
24- except IOError:
25- return None
26-
27- pid = get_pid()
28+ pid = read_pidfile()
29 if not process_is_couchdb(pid) and start_if_not_running:
30- # pidfile is stale
31 # start CouchDB by running the startup script
32 print "Desktop CouchDB is not running; starting it.",
33 from desktopcouch import start_local_couchdb
34- start_local_couchdb.start_couchdb()
35- # give the process a chance to start
36- for timeout in xrange(1000):
37- pid = get_pid()
38- if process_is_couchdb(pid):
39- break
40- print ".",
41- time.sleep(0.1)
42+ pid = start_local_couchdb.start_couchdb()
43 # now load the design documents, because it's started
44 start_local_couchdb.update_design_documents()
45
46- if process_is_couchdb(pid):
47- print " done."
48- else:
49- print " failed."
50+ if not process_is_couchdb(pid):
51 raise RuntimeError("desktop-couch not started")
52
53 return pid
54
55-def process_is_couchdb__linux(pid):
56- if not isinstance(pid, int):
57- return False
58+def find_port__linux(pid=None):
59+ if pid is None:
60+ pid = find_pid()
61
62 proc_dir = "/proc/%s" % (pid,)
63
64+ # enumerate the process' file descriptors
65+ fd_dir = os.path.join(proc_dir, 'fd')
66 try:
67- # check to make sure it is actually a desktop-couch instance
68- with open(os.path.join(proc_dir, 'cmdline')) as cmd_file:
69- cmd = cmd_file.read()
70- if '/desktop-couch' not in cmd:
71- return False
72-
73- # make sure it's our process.
74- if not os.access(os.path.join(proc_dir, "mem"), os.W_OK):
75- return False
76-
77- except IOError:
78- return False
79-
80- return True
81-
82-def find_port():
83- """Look in the CouchDB log to find the port number."""
84-
85- from desktopcouch.local_files import FILE_LOG
86- with open(FILE_LOG, 'r') as log_file:
87- port = None
88- try:
89- last_line = [
90- line for line in log_file if
91- 'Apache CouchDB has started on http://' in line][-1]
92- except IndexError:
93- return None
94- port = last_line.split(':')[-1].strip()
95- if port.endswith('/'):
96- port = port[:-1]
97- return int(port)
98+ fd_paths = [os.readlink(os.path.join(fd_dir, fd))
99+ for fd in os.listdir(fd_dir)]
100+ except OSError:
101+ raise RuntimeError("Unable to find file descriptors in /proc")
102+
103+ # identify socket fds
104+ socket_matches = [re.match('socket:\\[([0-9]+)\\]', p) for p in fd_paths]
105+ # extract their inode numbers
106+ socket_inodes = [m.group(1) for m in socket_matches if m is not None]
107+
108+ # construct a subexpression which matches any one of these inodes
109+ inode_subexp = "|".join(map(re.escape, socket_inodes))
110+ # construct regexp to match /proc/net/tcp entries which are listening
111+ # sockets having one of the given inode numbers
112+ listening_regexp = re.compile(r'''
113+ \s*\d+:\s* # sl
114+ [0-9A-F]{8}: # local_address part 1
115+ ([0-9A-F]{4})\s+ # local_address part 2
116+ 00000000:0000\s+ # rem_address
117+ 0A\s+ # st (0A = listening)
118+ [0-9A-F]{8}: # tx_queue
119+ [0-9A-F]{8}\s+ # rx_queue
120+ [0-9A-F]{2}: # tr
121+ [0-9A-F]{8}\s+ # tm->when
122+ [0-9A-F]{8}\s* # retrnsmt
123+ \d+\s+\d+\s+ # uid, timeout
124+ (?:%s)\s+ # inode
125+ ''' % (inode_subexp,), re.VERBOSE)
126+
127+ # extract the TCP port from the first matching line in /proc/$pid/net/tcp
128+ port = None
129+ with open(os.path.join(proc_dir, 'net', 'tcp')) as tcp_file:
130+ for line in tcp_file:
131+ match = listening_regexp.match(line)
132+ if match is not None:
133+ port = str(int(match.group(1), 16))
134+ break
135+ if port is None:
136+ raise RuntimeError("Unable to find listening port")
137+
138+ return port
139+
140
141
142 import platform
143 os_name = platform.system()
144 try:
145- process_is_couchdb = {
146- "Linux": process_is_couchdb__linux
147+ find_port = {
148+ "Linux": find_port__linux
149 } [os_name]
150 except KeyError:
151 raise NotImplementedError("os %r is not yet supported" % (os_name,))
152
153=== modified file 'desktopcouch/start_local_couchdb.py'
154--- desktopcouch/start_local_couchdb.py 2009-08-25 18:05:09 +0000
155+++ desktopcouch/start_local_couchdb.py 2009-09-01 00:38:53 +0000
156@@ -138,8 +138,52 @@
157 True)
158 return (admin_account_username, admin_account_basic_auth_password)
159
160+
161+def process_is_couchdb__linux(pid):
162+ if pid is None:
163+ return False
164+ pid = int(pid) # ensure it's a number
165+ proc_dir = "/proc/%d" % (pid,)
166+ try:
167+ # check to make sure it is actually a desktop-couch instance
168+ with open(os.path.join(proc_dir, 'cmdline')) as cmd_file:
169+ cmd = cmd_file.read()
170+ #if '/desktop-couch' not in cmd:
171+ if 'beam' not in cmd:
172+ return False
173+
174+ # make sure it's our process.
175+ if not os.access(os.path.join(proc_dir, "mem"), os.W_OK):
176+ return False
177+
178+ except IOError:
179+ return False
180+
181+ return True
182+
183+import platform
184+os_name = platform.system()
185+try:
186+ process_is_couchdb = {
187+ "Linux": process_is_couchdb__linux
188+ } [os_name]
189+except KeyError:
190+ raise NotImplementedError("os %r is not yet supported" % (os_name,))
191+
192+def read_pidfile():
193+ try:
194+ pid_file = local_files.FILE_PID
195+ with open(pid_file) as fp:
196+ try:
197+ contents = fp.read()
198+ return int(contents)
199+ except ValueError:
200+ return None
201+ except IOError:
202+ return None
203+
204 def run_couchdb():
205- """Actually start the CouchDB process"""
206+ """Actually start the CouchDB process. Return its PID."""
207 local_exec = local_files.COUCH_EXEC_COMMAND + ['-b']
208 try:
209 # subprocess is buggy. Chad patched, but that takes time to propagate.
210@@ -160,6 +204,15 @@
211 print >> sys.stderr, "Execution failed: %s: %s" % (e, local_exec)
212 exit(1)
213
214+ # give the process a chance to start
215+ for timeout in xrange(1000):
216+ pid = read_pidfile()
217+ time.sleep(0.3)
218+ if pid is not None and process_is_couchdb(pid):
219+ break
220+ print "...waiting for couchdb to start..."
221+ return pid
222+
223 def update_design_documents():
224 """Check system design documents and update any that need updating
225
226@@ -202,7 +255,7 @@
227 # than inefficiently just overwriting it regardless
228 db.add_view(view_name, mapjs, reducejs, dd_name)
229
230-def write_bookmark_file(username, password):
231+def write_bookmark_file(username, password, pid):
232 """Write out an HTML document that the user can bookmark to find their DB"""
233 bookmark_file = os.path.join(local_files.DIR_DB, "couchdb.html")
234
235@@ -221,19 +274,7 @@
236 html = fp.read()
237 fp.close()
238
239- port = None
240- for retry in xrange(10000, 0, -1):
241- pid = desktopcouch.find_pid(start_if_not_running=False)
242- try:
243- port = desktopcouch.find_port()
244- if not port is None:
245- break
246- except IOError:
247- if retry == 1:
248- raise
249- time.sleep(0.1)
250- continue
251-
252+ port = desktopcouch.find_port(pid=pid)
253 if port is None:
254 print ("We couldn't find desktop-CouchDB's network port. Bookmark "
255 "file not written.")
256@@ -252,15 +293,16 @@
257 os.path.realpath(bookmark_file)
258
259 def start_couchdb():
260- """Execute each step to start a desktop CouchDB"""
261+ """Execute each step to start a desktop CouchDB."""
262 username, password = create_ini_file()
263- run_couchdb()
264+ pid = run_couchdb()
265 # Note that we do not call update_design_documents here. This is because
266 # Couch won't actually have started yet, so when update_design_documents
267 # calls the Records API, that will call back into get_port and we end up
268 # starting Couch again. Instead, get_port calls update_design_documents
269 # *after* Couch startup has occurred.
270- write_bookmark_file(username, password)
271+ write_bookmark_file(username, password, pid)
272+ return pid
273
274
275 if __name__ == "__main__":

Subscribers

People subscribed via source and target branches