Mercurial > hg
annotate hgext/inotify/server.py @ 8324:b923d599c309
inotify: inotify.server.walk*() remove unnecessary var
Remove hginside var and the test it relates to:
not( top or not hginside ) == (not top) and hginside, so the only case
when nothing will be yielded is when hginside is True and top is False.
Because of the returns placed upstream, this case will not happen anymore.
We can then safely remove hginside and the (if)s
author | Nicolas Dumazet <nicdumz.commits@gmail.com> |
---|---|
date | Mon, 04 May 2009 17:58:26 +0900 |
parents | 589a82fb02a2 |
children | f2559645643a |
rev | line source |
---|---|
6239 | 1 # server.py - inotify status server |
2 # | |
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com> | |
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com> | |
5 # | |
8225
46293a0c7e9f
updated license to be explicit about GPL version 2
Martin Geisler <mg@lazybytes.net>
parents:
8209
diff
changeset
|
6 # This software may be used and distributed according to the terms of the |
46293a0c7e9f
updated license to be explicit about GPL version 2
Martin Geisler <mg@lazybytes.net>
parents:
8209
diff
changeset
|
7 # GNU General Public License version 2, incorporated herein by reference. |
6239 | 8 |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
9 from mercurial.i18n import _ |
7420
b4ac1e2cd38c
inotify: remove unused imports (thanks pyflakes)
Brendan Cully <brendan@kublai.com>
parents:
7351
diff
changeset
|
10 from mercurial import osutil, util |
6239 | 11 import common |
6997
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
12 import errno, os, select, socket, stat, struct, sys, tempfile, time |
6239 | 13 |
14 try: | |
6994
bf727bab38b9
Use relative imports in inotify.server.
Brendan Cully <brendan@kublai.com>
parents:
6287
diff
changeset
|
15 import linux as inotify |
bf727bab38b9
Use relative imports in inotify.server.
Brendan Cully <brendan@kublai.com>
parents:
6287
diff
changeset
|
16 from linux import watcher |
6239 | 17 except ImportError: |
18 raise | |
19 | |
20 class AlreadyStartedException(Exception): pass | |
21 | |
22 def join(a, b): | |
23 if a: | |
24 if a[-1] == '/': | |
25 return a + b | |
26 return a + '/' + b | |
27 return b | |
28 | |
29 walk_ignored_errors = (errno.ENOENT, errno.ENAMETOOLONG) | |
30 | |
31 def walkrepodirs(repo): | |
32 '''Iterate over all subdirectories of this repo. | |
33 Exclude the .hg directory, any nested repos, and ignored dirs.''' | |
34 rootslash = repo.root + os.sep | |
8322
3c6c21eb3416
inotify: inotify.server.walkrepodirs() simplify walking
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8321
diff
changeset
|
35 |
6239 | 36 def walkit(dirname, top): |
8321
ec985dcfd7da
inotify: inotify.server.walkrepodirs() simplify
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8320
diff
changeset
|
37 fullpath = rootslash + dirname |
6239 | 38 try: |
8321
ec985dcfd7da
inotify: inotify.server.walkrepodirs() simplify
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8320
diff
changeset
|
39 for name, kind in osutil.listdir(fullpath): |
6239 | 40 if kind == stat.S_IFDIR: |
41 if name == '.hg': | |
8323
589a82fb02a2
inotify: inotify.server.walk*() cleanup
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8322
diff
changeset
|
42 if not top: |
589a82fb02a2
inotify: inotify.server.walk*() cleanup
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8322
diff
changeset
|
43 return |
6239 | 44 else: |
45 d = join(dirname, name) | |
46 if repo.dirstate._ignore(d): | |
47 continue | |
8322
3c6c21eb3416
inotify: inotify.server.walkrepodirs() simplify walking
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8321
diff
changeset
|
48 for subdir in walkit(d, False): |
3c6c21eb3416
inotify: inotify.server.walkrepodirs() simplify walking
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8321
diff
changeset
|
49 yield subdir |
6239 | 50 except OSError, err: |
51 if err.errno not in walk_ignored_errors: | |
52 raise | |
8324
b923d599c309
inotify: inotify.server.walk*() remove unnecessary var
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8323
diff
changeset
|
53 yield fullpath |
8322
3c6c21eb3416
inotify: inotify.server.walkrepodirs() simplify walking
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8321
diff
changeset
|
54 |
3c6c21eb3416
inotify: inotify.server.walkrepodirs() simplify walking
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8321
diff
changeset
|
55 return walkit('', True) |
6239 | 56 |
57 def walk(repo, root): | |
58 '''Like os.walk, but only yields regular files.''' | |
59 | |
60 # This function is critical to performance during startup. | |
61 | |
62 rootslash = repo.root + os.sep | |
63 | |
64 def walkit(root, reporoot): | |
65 files, dirs = [], [] | |
66 | |
67 try: | |
68 fullpath = rootslash + root | |
69 for name, kind in osutil.listdir(fullpath): | |
70 if kind == stat.S_IFDIR: | |
71 if name == '.hg': | |
72 if reporoot: | |
73 continue | |
74 else: | |
8323
589a82fb02a2
inotify: inotify.server.walk*() cleanup
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8322
diff
changeset
|
75 return |
6239 | 76 dirs.append(name) |
77 elif kind in (stat.S_IFREG, stat.S_IFLNK): | |
78 files.append((name, kind)) | |
8324
b923d599c309
inotify: inotify.server.walk*() remove unnecessary var
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8323
diff
changeset
|
79 yield fullpath, dirs, files |
6239 | 80 |
81 for subdir in dirs: | |
82 path = join(root, subdir) | |
83 if repo.dirstate._ignore(path): | |
84 continue | |
85 for result in walkit(path, False): | |
8320
a1305c1c8d8e
inotify: inotify.server.walk() simplify algorithm
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8319
diff
changeset
|
86 yield result |
6239 | 87 except OSError, err: |
88 if err.errno not in walk_ignored_errors: | |
89 raise | |
8320
a1305c1c8d8e
inotify: inotify.server.walk() simplify algorithm
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8319
diff
changeset
|
90 |
a1305c1c8d8e
inotify: inotify.server.walk() simplify algorithm
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
8319
diff
changeset
|
91 return walkit(root, root == '') |
6239 | 92 |
93 def _explain_watch_limit(ui, repo, count): | |
94 path = '/proc/sys/fs/inotify/max_user_watches' | |
95 try: | |
96 limit = int(file(path).read()) | |
97 except IOError, err: | |
98 if err.errno != errno.ENOENT: | |
99 raise | |
100 raise util.Abort(_('this system does not seem to ' | |
101 'support inotify')) | |
102 ui.warn(_('*** the current per-user limit on the number ' | |
103 'of inotify watches is %s\n') % limit) | |
104 ui.warn(_('*** this limit is too low to watch every ' | |
105 'directory in this repository\n')) | |
106 ui.warn(_('*** counting directories: ')) | |
107 ndirs = len(list(walkrepodirs(repo))) | |
108 ui.warn(_('found %d\n') % ndirs) | |
109 newlimit = min(limit, 1024) | |
110 while newlimit < ((limit + ndirs) * 1.1): | |
111 newlimit *= 2 | |
112 ui.warn(_('*** to raise the limit from %d to %d (run as root):\n') % | |
113 (limit, newlimit)) | |
114 ui.warn(_('*** echo %d > %s\n') % (newlimit, path)) | |
115 raise util.Abort(_('cannot watch %s until inotify watch limit is raised') | |
116 % repo.root) | |
117 | |
118 class Watcher(object): | |
119 poll_events = select.POLLIN | |
120 statuskeys = 'almr!?' | |
121 | |
122 def __init__(self, ui, repo, master): | |
123 self.ui = ui | |
124 self.repo = repo | |
125 self.wprefix = self.repo.wjoin('') | |
126 self.timeout = None | |
127 self.master = master | |
128 self.mask = ( | |
129 inotify.IN_ATTRIB | | |
130 inotify.IN_CREATE | | |
131 inotify.IN_DELETE | | |
132 inotify.IN_DELETE_SELF | | |
133 inotify.IN_MODIFY | | |
134 inotify.IN_MOVED_FROM | | |
135 inotify.IN_MOVED_TO | | |
136 inotify.IN_MOVE_SELF | | |
137 inotify.IN_ONLYDIR | | |
138 inotify.IN_UNMOUNT | | |
139 0) | |
140 try: | |
141 self.watcher = watcher.Watcher() | |
142 except OSError, err: | |
143 raise util.Abort(_('inotify service not available: %s') % | |
144 err.strerror) | |
145 self.threshold = watcher.Threshold(self.watcher) | |
146 self.registered = True | |
147 self.fileno = self.watcher.fileno | |
148 | |
149 self.repo.dirstate.__class__.inotifyserver = True | |
150 | |
151 self.tree = {} | |
152 self.statcache = {} | |
153 self.statustrees = dict([(s, {}) for s in self.statuskeys]) | |
154 | |
155 self.watches = 0 | |
156 self.last_event = None | |
157 | |
158 self.eventq = {} | |
159 self.deferred = 0 | |
160 | |
161 self.ds_info = self.dirstate_info() | |
162 self.scan() | |
163 | |
164 def event_time(self): | |
165 last = self.last_event | |
166 now = time.time() | |
167 self.last_event = now | |
168 | |
169 if last is None: | |
170 return 'start' | |
171 delta = now - last | |
172 if delta < 5: | |
173 return '+%.3f' % delta | |
174 if delta < 50: | |
175 return '+%.2f' % delta | |
176 return '+%.1f' % delta | |
177 | |
178 def dirstate_info(self): | |
179 try: | |
180 st = os.lstat(self.repo.join('dirstate')) | |
181 return st.st_mtime, st.st_ino | |
182 except OSError, err: | |
183 if err.errno != errno.ENOENT: | |
184 raise | |
185 return 0, 0 | |
186 | |
187 def add_watch(self, path, mask): | |
188 if not path: | |
189 return | |
190 if self.watcher.path(path) is None: | |
191 if self.ui.debugflag: | |
192 self.ui.note(_('watching %r\n') % path[len(self.wprefix):]) | |
193 try: | |
194 self.watcher.add(path, mask) | |
195 self.watches += 1 | |
196 except OSError, err: | |
197 if err.errno in (errno.ENOENT, errno.ENOTDIR): | |
198 return | |
199 if err.errno != errno.ENOSPC: | |
200 raise | |
201 _explain_watch_limit(self.ui, self.repo, self.watches) | |
202 | |
203 def setup(self): | |
204 self.ui.note(_('watching directories under %r\n') % self.repo.root) | |
205 self.add_watch(self.repo.path, inotify.IN_DELETE) | |
206 self.check_dirstate() | |
207 | |
208 def wpath(self, evt): | |
209 path = evt.fullpath | |
210 if path == self.repo.root: | |
211 return '' | |
212 if path.startswith(self.wprefix): | |
213 return path[len(self.wprefix):] | |
214 raise 'wtf? ' + path | |
215 | |
216 def dir(self, tree, path): | |
217 if path: | |
218 for name in path.split('/'): | |
7351
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
219 tree.setdefault(name, {}) |
6239 | 220 tree = tree[name] |
221 return tree | |
222 | |
223 def lookup(self, path, tree): | |
224 if path: | |
225 try: | |
226 for name in path.split('/'): | |
227 tree = tree[name] | |
228 except KeyError: | |
7351
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
229 return 'x' |
6239 | 230 except TypeError: |
7351
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
231 return 'd' |
6239 | 232 return tree |
233 | |
234 def split(self, path): | |
235 c = path.rfind('/') | |
236 if c == -1: | |
237 return '', path | |
238 return path[:c], path[c+1:] | |
239 | |
240 def filestatus(self, fn, st): | |
241 try: | |
242 type_, mode, size, time = self.repo.dirstate._map[fn][:4] | |
243 except KeyError: | |
244 type_ = '?' | |
245 if type_ == 'n': | |
246 if not st: | |
247 return '!' | |
248 st_mode, st_size, st_mtime = st | |
7082
be81b4788115
inotify: fix confusion on files in lookup state
Matt Mackall <mpm@selenic.com>
parents:
6998
diff
changeset
|
249 if size == -1: |
be81b4788115
inotify: fix confusion on files in lookup state
Matt Mackall <mpm@selenic.com>
parents:
6998
diff
changeset
|
250 return 'l' |
6239 | 251 if size and (size != st_size or (mode ^ st_mode) & 0100): |
252 return 'm' | |
253 if time != int(st_mtime): | |
254 return 'l' | |
255 return 'n' | |
256 if type_ in 'ma' and not st: | |
257 return '!' | |
258 if type_ == '?' and self.repo.dirstate._ignore(fn): | |
259 return 'i' | |
260 return type_ | |
261 | |
7086
4033195d455b
inotify: avoid status getting out of sync
Matt Mackall <mpm@selenic.com>
parents:
7085
diff
changeset
|
262 def updatestatus(self, wfn, st=None, status=None): |
6239 | 263 if st: |
264 status = self.filestatus(wfn, st) | |
265 else: | |
266 self.statcache.pop(wfn, None) | |
267 root, fn = self.split(wfn) | |
268 d = self.dir(self.tree, root) | |
7351
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
269 oldstatus = d.get(fn) |
6239 | 270 isdir = False |
271 if oldstatus: | |
272 try: | |
273 if not status: | |
274 if oldstatus in 'almn': | |
275 status = '!' | |
276 elif oldstatus == 'r': | |
277 status = 'r' | |
278 except TypeError: | |
279 # oldstatus may be a dict left behind by a deleted | |
280 # directory | |
281 isdir = True | |
282 else: | |
283 if oldstatus in self.statuskeys and oldstatus != status: | |
284 del self.dir(self.statustrees[oldstatus], root)[fn] | |
285 if self.ui.debugflag and oldstatus != status: | |
286 if isdir: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
287 self.ui.note(_('status: %r dir(%d) -> %s\n') % |
6239 | 288 (wfn, len(oldstatus), status)) |
289 else: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
290 self.ui.note(_('status: %r %s -> %s\n') % |
6239 | 291 (wfn, oldstatus, status)) |
292 if not isdir: | |
293 if status and status != 'i': | |
7351
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
294 d[fn] = status |
6239 | 295 if status in self.statuskeys: |
296 dd = self.dir(self.statustrees[status], root) | |
297 if oldstatus != status or fn not in dd: | |
7351
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
298 dd[fn] = status |
6239 | 299 else: |
300 d.pop(fn, None) | |
7892
67e59a9886d5
Fixing issue1542, adding a relevant test
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
7451
diff
changeset
|
301 elif not status: |
67e59a9886d5
Fixing issue1542, adding a relevant test
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
7451
diff
changeset
|
302 # a directory is being removed, check its contents |
67e59a9886d5
Fixing issue1542, adding a relevant test
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
7451
diff
changeset
|
303 for subfile, b in oldstatus.copy().iteritems(): |
67e59a9886d5
Fixing issue1542, adding a relevant test
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
7451
diff
changeset
|
304 self.updatestatus(wfn + '/' + subfile, None) |
67e59a9886d5
Fixing issue1542, adding a relevant test
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
7451
diff
changeset
|
305 |
6239 | 306 |
307 def check_deleted(self, key): | |
308 # Files that had been deleted but were present in the dirstate | |
309 # may have vanished from the dirstate; we must clean them up. | |
310 nuke = [] | |
311 for wfn, ignore in self.walk(key, self.statustrees[key]): | |
312 if wfn not in self.repo.dirstate: | |
313 nuke.append(wfn) | |
314 for wfn in nuke: | |
315 root, fn = self.split(wfn) | |
316 del self.dir(self.statustrees[key], root)[fn] | |
317 del self.dir(self.tree, root)[fn] | |
6287
c86207d41512
Spacing cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
6239
diff
changeset
|
318 |
6239 | 319 def scan(self, topdir=''): |
320 self.handle_timeout() | |
321 ds = self.repo.dirstate._map.copy() | |
322 self.add_watch(join(self.repo.root, topdir), self.mask) | |
323 for root, dirs, entries in walk(self.repo, topdir): | |
324 for d in dirs: | |
325 self.add_watch(join(root, d), self.mask) | |
326 wroot = root[len(self.wprefix):] | |
327 d = self.dir(self.tree, wroot) | |
328 for fn, kind in entries: | |
329 wfn = join(wroot, fn) | |
330 self.updatestatus(wfn, self.getstat(wfn)) | |
331 ds.pop(wfn, None) | |
332 wtopdir = topdir | |
333 if wtopdir and wtopdir[-1] != '/': | |
334 wtopdir += '/' | |
335 for wfn, state in ds.iteritems(): | |
336 if not wfn.startswith(wtopdir): | |
337 continue | |
7302
972737252d05
inotify: server raising an error when removing a file (issue1371)
Gerard Korsten <soonkia77@gmail.com>
parents:
7280
diff
changeset
|
338 try: |
972737252d05
inotify: server raising an error when removing a file (issue1371)
Gerard Korsten <soonkia77@gmail.com>
parents:
7280
diff
changeset
|
339 st = self.stat(wfn) |
972737252d05
inotify: server raising an error when removing a file (issue1371)
Gerard Korsten <soonkia77@gmail.com>
parents:
7280
diff
changeset
|
340 except OSError: |
972737252d05
inotify: server raising an error when removing a file (issue1371)
Gerard Korsten <soonkia77@gmail.com>
parents:
7280
diff
changeset
|
341 status = state[0] |
972737252d05
inotify: server raising an error when removing a file (issue1371)
Gerard Korsten <soonkia77@gmail.com>
parents:
7280
diff
changeset
|
342 self.updatestatus(wfn, None, status=status) |
6239 | 343 else: |
7086
4033195d455b
inotify: avoid status getting out of sync
Matt Mackall <mpm@selenic.com>
parents:
7085
diff
changeset
|
344 self.updatestatus(wfn, st) |
6239 | 345 self.check_deleted('!') |
346 self.check_deleted('r') | |
347 | |
348 def check_dirstate(self): | |
349 ds_info = self.dirstate_info() | |
350 if ds_info == self.ds_info: | |
351 return | |
352 self.ds_info = ds_info | |
353 if not self.ui.debugflag: | |
354 self.last_event = None | |
355 self.ui.note(_('%s dirstate reload\n') % self.event_time()) | |
356 self.repo.dirstate.invalidate() | |
357 self.scan() | |
358 self.ui.note(_('%s end dirstate reload\n') % self.event_time()) | |
359 | |
360 def walk(self, states, tree, prefix=''): | |
361 # This is the "inner loop" when talking to the client. | |
6287
c86207d41512
Spacing cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
6239
diff
changeset
|
362 |
6239 | 363 for name, val in tree.iteritems(): |
364 path = join(prefix, name) | |
7351
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
365 try: |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
366 if val in states: |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
367 yield path, val |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
368 except TypeError: |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
369 for p in self.walk(states, val, path): |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
370 yield p |
6239 | 371 |
372 def update_hgignore(self): | |
373 # An update of the ignore file can potentially change the | |
374 # states of all unknown and ignored files. | |
375 | |
376 # XXX If the user has other ignore files outside the repo, or | |
377 # changes their list of ignore files at run time, we'll | |
378 # potentially never see changes to them. We could get the | |
379 # client to report to us what ignore data they're using. | |
380 # But it's easier to do nothing than to open that can of | |
381 # worms. | |
382 | |
7085
1fcc282e2c43
inotify: fixup rebuilding ignore
Matt Mackall <mpm@selenic.com>
parents:
7082
diff
changeset
|
383 if '_ignore' in self.repo.dirstate.__dict__: |
1fcc282e2c43
inotify: fixup rebuilding ignore
Matt Mackall <mpm@selenic.com>
parents:
7082
diff
changeset
|
384 delattr(self.repo.dirstate, '_ignore') |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
385 self.ui.note(_('rescanning due to .hgignore change\n')) |
6239 | 386 self.scan() |
6287
c86207d41512
Spacing cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
6239
diff
changeset
|
387 |
6239 | 388 def getstat(self, wpath): |
389 try: | |
390 return self.statcache[wpath] | |
391 except KeyError: | |
392 try: | |
393 return self.stat(wpath) | |
394 except OSError, err: | |
395 if err.errno != errno.ENOENT: | |
396 raise | |
6287
c86207d41512
Spacing cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
6239
diff
changeset
|
397 |
6239 | 398 def stat(self, wpath): |
399 try: | |
400 st = os.lstat(join(self.wprefix, wpath)) | |
401 ret = st.st_mode, st.st_size, st.st_mtime | |
402 self.statcache[wpath] = ret | |
403 return ret | |
7280
810ca383da9c
remove unused variables
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7220
diff
changeset
|
404 except OSError: |
6239 | 405 self.statcache.pop(wpath, None) |
406 raise | |
6287
c86207d41512
Spacing cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
6239
diff
changeset
|
407 |
6239 | 408 def created(self, wpath): |
409 if wpath == '.hgignore': | |
410 self.update_hgignore() | |
411 try: | |
412 st = self.stat(wpath) | |
413 if stat.S_ISREG(st[0]): | |
414 self.updatestatus(wpath, st) | |
7280
810ca383da9c
remove unused variables
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7220
diff
changeset
|
415 except OSError: |
6239 | 416 pass |
417 | |
418 def modified(self, wpath): | |
419 if wpath == '.hgignore': | |
420 self.update_hgignore() | |
421 try: | |
422 st = self.stat(wpath) | |
423 if stat.S_ISREG(st[0]): | |
424 if self.repo.dirstate[wpath] in 'lmn': | |
425 self.updatestatus(wpath, st) | |
426 except OSError: | |
427 pass | |
428 | |
429 def deleted(self, wpath): | |
430 if wpath == '.hgignore': | |
431 self.update_hgignore() | |
432 elif wpath.startswith('.hg/'): | |
433 if wpath == '.hg/wlock': | |
434 self.check_dirstate() | |
435 return | |
436 | |
437 self.updatestatus(wpath, None) | |
6287
c86207d41512
Spacing cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
6239
diff
changeset
|
438 |
6239 | 439 def schedule_work(self, wpath, evt): |
440 self.eventq.setdefault(wpath, []) | |
441 prev = self.eventq[wpath] | |
442 try: | |
443 if prev and evt == 'm' and prev[-1] in 'cm': | |
444 return | |
445 self.eventq[wpath].append(evt) | |
446 finally: | |
447 self.deferred += 1 | |
448 self.timeout = 250 | |
449 | |
450 def deferred_event(self, wpath, evt): | |
451 if evt == 'c': | |
452 self.created(wpath) | |
453 elif evt == 'm': | |
454 self.modified(wpath) | |
455 elif evt == 'd': | |
456 self.deleted(wpath) | |
6287
c86207d41512
Spacing cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
6239
diff
changeset
|
457 |
6239 | 458 def process_create(self, wpath, evt): |
459 if self.ui.debugflag: | |
460 self.ui.note(_('%s event: created %s\n') % | |
461 (self.event_time(), wpath)) | |
462 | |
463 if evt.mask & inotify.IN_ISDIR: | |
464 self.scan(wpath) | |
465 else: | |
466 self.schedule_work(wpath, 'c') | |
467 | |
468 def process_delete(self, wpath, evt): | |
469 if self.ui.debugflag: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
470 self.ui.note(_('%s event: deleted %s\n') % |
6239 | 471 (self.event_time(), wpath)) |
472 | |
473 if evt.mask & inotify.IN_ISDIR: | |
474 self.scan(wpath) | |
7892
67e59a9886d5
Fixing issue1542, adding a relevant test
Nicolas Dumazet <nicdumz.commits@gmail.com>
parents:
7451
diff
changeset
|
475 self.schedule_work(wpath, 'd') |
6239 | 476 |
477 def process_modify(self, wpath, evt): | |
478 if self.ui.debugflag: | |
479 self.ui.note(_('%s event: modified %s\n') % | |
480 (self.event_time(), wpath)) | |
481 | |
482 if not (evt.mask & inotify.IN_ISDIR): | |
483 self.schedule_work(wpath, 'm') | |
484 | |
485 def process_unmount(self, evt): | |
486 self.ui.warn(_('filesystem containing %s was unmounted\n') % | |
487 evt.fullpath) | |
488 sys.exit(0) | |
489 | |
490 def handle_event(self, fd, event): | |
491 if self.ui.debugflag: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
492 self.ui.note(_('%s readable: %d bytes\n') % |
6239 | 493 (self.event_time(), self.threshold.readable())) |
494 if not self.threshold(): | |
495 if self.registered: | |
496 if self.ui.debugflag: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
497 self.ui.note(_('%s below threshold - unhooking\n') % |
6239 | 498 (self.event_time())) |
499 self.master.poll.unregister(fd) | |
500 self.registered = False | |
501 self.timeout = 250 | |
502 else: | |
503 self.read_events() | |
504 | |
505 def read_events(self, bufsize=None): | |
506 events = self.watcher.read(bufsize) | |
507 if self.ui.debugflag: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
508 self.ui.note(_('%s reading %d events\n') % |
6239 | 509 (self.event_time(), len(events))) |
510 for evt in events: | |
511 wpath = self.wpath(evt) | |
512 if evt.mask & inotify.IN_UNMOUNT: | |
513 self.process_unmount(wpath, evt) | |
514 elif evt.mask & (inotify.IN_MODIFY | inotify.IN_ATTRIB): | |
515 self.process_modify(wpath, evt) | |
516 elif evt.mask & (inotify.IN_DELETE | inotify.IN_DELETE_SELF | | |
517 inotify.IN_MOVED_FROM): | |
518 self.process_delete(wpath, evt) | |
519 elif evt.mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO): | |
520 self.process_create(wpath, evt) | |
521 | |
522 def handle_timeout(self): | |
523 if not self.registered: | |
524 if self.ui.debugflag: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
525 self.ui.note(_('%s hooking back up with %d bytes readable\n') % |
6239 | 526 (self.event_time(), self.threshold.readable())) |
527 self.read_events(0) | |
528 self.master.poll.register(self, select.POLLIN) | |
529 self.registered = True | |
530 | |
531 if self.eventq: | |
532 if self.ui.debugflag: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
533 self.ui.note(_('%s processing %d deferred events as %d\n') % |
6239 | 534 (self.event_time(), self.deferred, |
535 len(self.eventq))) | |
8209
a1a5a57efe90
replace util.sort with sorted built-in
Matt Mackall <mpm@selenic.com>
parents:
7892
diff
changeset
|
536 for wpath, evts in sorted(self.eventq.iteritems()): |
6239 | 537 for evt in evts: |
538 self.deferred_event(wpath, evt) | |
539 self.eventq.clear() | |
540 self.deferred = 0 | |
541 self.timeout = None | |
542 | |
543 def shutdown(self): | |
544 self.watcher.close() | |
545 | |
546 class Server(object): | |
547 poll_events = select.POLLIN | |
548 | |
549 def __init__(self, ui, repo, watcher, timeout): | |
550 self.ui = ui | |
551 self.repo = repo | |
552 self.watcher = watcher | |
553 self.timeout = timeout | |
554 self.sock = socket.socket(socket.AF_UNIX) | |
555 self.sockpath = self.repo.join('inotify.sock') | |
6997
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
556 self.realsockpath = None |
6239 | 557 try: |
558 self.sock.bind(self.sockpath) | |
559 except socket.error, err: | |
560 if err[0] == errno.EADDRINUSE: | |
6997
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
561 raise AlreadyStartedException(_('could not start server: %s') |
6239 | 562 % err[1]) |
6997
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
563 if err[0] == "AF_UNIX path too long": |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
564 tempdir = tempfile.mkdtemp(prefix="hg-inotify-") |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
565 self.realsockpath = os.path.join(tempdir, "inotify.sock") |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
566 try: |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
567 self.sock.bind(self.realsockpath) |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
568 os.symlink(self.realsockpath, self.sockpath) |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
569 except (OSError, socket.error), inst: |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
570 try: |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
571 os.unlink(self.realsockpath) |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
572 except: |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
573 pass |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
574 os.rmdir(tempdir) |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
575 if inst.errno == errno.EEXIST: |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
576 raise AlreadyStartedException(_('could not start server: %s') |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
577 % inst.strerror) |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
578 raise |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
579 else: |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
580 raise |
6239 | 581 self.sock.listen(5) |
582 self.fileno = self.sock.fileno | |
583 | |
584 def handle_timeout(self): | |
585 pass | |
586 | |
587 def handle_event(self, fd, event): | |
588 sock, addr = self.sock.accept() | |
589 | |
590 cs = common.recvcs(sock) | |
591 version = ord(cs.read(1)) | |
592 | |
593 sock.sendall(chr(common.version)) | |
594 | |
595 if version != common.version: | |
596 self.ui.warn(_('received query from incompatible client ' | |
597 'version %d\n') % version) | |
598 return | |
599 | |
600 names = cs.read().split('\0') | |
6287
c86207d41512
Spacing cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
6239
diff
changeset
|
601 |
6239 | 602 states = names.pop() |
603 | |
604 self.ui.note(_('answering query for %r\n') % states) | |
605 | |
606 if self.watcher.timeout: | |
607 # We got a query while a rescan is pending. Make sure we | |
608 # rescan before responding, or we could give back a wrong | |
609 # answer. | |
610 self.watcher.handle_timeout() | |
611 | |
612 if not names: | |
613 def genresult(states, tree): | |
614 for fn, state in self.watcher.walk(states, tree): | |
615 yield fn | |
616 else: | |
617 def genresult(states, tree): | |
618 for fn in names: | |
619 l = self.watcher.lookup(fn, tree) | |
7351
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
620 try: |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
621 if l in states: |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
622 yield fn |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
623 except TypeError: |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
624 for f, s in self.watcher.walk(states, l, fn): |
5ab0abf27dd9
Backed out changeset c5dbe86b0fee (issue1375)
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
7350
diff
changeset
|
625 yield f |
6239 | 626 |
627 results = ['\0'.join(r) for r in [ | |
628 genresult('l', self.watcher.statustrees['l']), | |
629 genresult('m', self.watcher.statustrees['m']), | |
630 genresult('a', self.watcher.statustrees['a']), | |
631 genresult('r', self.watcher.statustrees['r']), | |
632 genresult('!', self.watcher.statustrees['!']), | |
633 '?' in states and genresult('?', self.watcher.statustrees['?']) or [], | |
634 [], | |
635 'c' in states and genresult('n', self.watcher.tree) or [], | |
636 ]] | |
637 | |
638 try: | |
639 try: | |
640 sock.sendall(struct.pack(common.resphdrfmt, | |
641 *map(len, results))) | |
642 sock.sendall(''.join(results)) | |
643 finally: | |
644 sock.shutdown(socket.SHUT_WR) | |
645 except socket.error, err: | |
646 if err[0] != errno.EPIPE: | |
647 raise | |
648 | |
649 def shutdown(self): | |
650 self.sock.close() | |
651 try: | |
652 os.unlink(self.sockpath) | |
6997
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
653 if self.realsockpath: |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
654 os.unlink(self.realsockpath) |
9c4e488f105e
inotify: workaround ENAMETOOLONG by using symlinks
Benoit Boissinot <benoit.boissinot@ens-lyon.org>
parents:
6994
diff
changeset
|
655 os.rmdir(os.path.dirname(self.realsockpath)) |
6239 | 656 except OSError, err: |
657 if err.errno != errno.ENOENT: | |
658 raise | |
659 | |
660 class Master(object): | |
661 def __init__(self, ui, repo, timeout=None): | |
662 self.ui = ui | |
663 self.repo = repo | |
664 self.poll = select.poll() | |
665 self.watcher = Watcher(ui, repo, self) | |
666 self.server = Server(ui, repo, self.watcher, timeout) | |
667 self.table = {} | |
668 for obj in (self.watcher, self.server): | |
669 fd = obj.fileno() | |
670 self.table[fd] = obj | |
671 self.poll.register(fd, obj.poll_events) | |
672 | |
673 def register(self, fd, mask): | |
674 self.poll.register(fd, mask) | |
675 | |
676 def shutdown(self): | |
677 for obj in self.table.itervalues(): | |
678 obj.shutdown() | |
679 | |
680 def run(self): | |
681 self.watcher.setup() | |
682 self.ui.note(_('finished setup\n')) | |
683 if os.getenv('TIME_STARTUP'): | |
684 sys.exit(0) | |
685 while True: | |
686 timeout = None | |
687 timeobj = None | |
688 for obj in self.table.itervalues(): | |
689 if obj.timeout is not None and (timeout is None or obj.timeout < timeout): | |
690 timeout, timeobj = obj.timeout, obj | |
691 try: | |
692 if self.ui.debugflag: | |
693 if timeout is None: | |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
694 self.ui.note(_('polling: no timeout\n')) |
6239 | 695 else: |
6961
12163fb21fce
i18n: mark strings for translation in inotify extension
Martin Geisler <mg@daimi.au.dk>
parents:
6909
diff
changeset
|
696 self.ui.note(_('polling: %sms timeout\n') % timeout) |
6239 | 697 events = self.poll.poll(timeout) |
698 except select.error, err: | |
699 if err[0] == errno.EINTR: | |
700 continue | |
701 raise | |
702 if events: | |
703 for fd, event in events: | |
704 self.table[fd].handle_event(fd, event) | |
705 elif timeobj: | |
706 timeobj.handle_timeout() | |
707 | |
708 def start(ui, repo): | |
7451
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
709 def closefds(ignore): |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
710 # (from python bug #1177468) |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
711 # close all inherited file descriptors |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
712 # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
713 # a file descriptor is kept internally as os._urandomfd (created on demand |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
714 # the first time os.urandom() is called), and should not be closed |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
715 try: |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
716 os.urandom(4) |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
717 urandom_fd = getattr(os, '_urandomfd', None) |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
718 except AttributeError: |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
719 urandom_fd = None |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
720 ignore.append(urandom_fd) |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
721 for fd in range(3, 256): |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
722 if fd in ignore: |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
723 continue |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
724 try: |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
725 os.close(fd) |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
726 except OSError: |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
727 pass |
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
728 |
6239 | 729 m = Master(ui, repo) |
730 sys.stdout.flush() | |
731 sys.stderr.flush() | |
732 | |
733 pid = os.fork() | |
734 if pid: | |
735 return pid | |
736 | |
7451
fca9947652ce
inotify: close most file descriptors when autostarting
Brendan Cully <brendan@kublai.com>
parents:
7420
diff
changeset
|
737 closefds([m.server.fileno(), m.watcher.fileno()]) |
6239 | 738 os.setsid() |
739 | |
740 fd = os.open('/dev/null', os.O_RDONLY) | |
741 os.dup2(fd, 0) | |
742 if fd > 0: | |
743 os.close(fd) | |
744 | |
745 fd = os.open(ui.config('inotify', 'log', '/dev/null'), | |
746 os.O_RDWR | os.O_CREAT | os.O_TRUNC) | |
747 os.dup2(fd, 1) | |
748 os.dup2(fd, 2) | |
749 if fd > 2: | |
750 os.close(fd) | |
751 | |
752 try: | |
753 m.run() | |
754 finally: | |
755 m.shutdown() | |
756 os._exit(0) |