Merge lp:~lifeless/python-oops-datedir-repo/bug-971255 into lp:python-oops-datedir-repo

Proposed by Robert Collins
Status: Merged
Merged at revision: 42
Proposed branch: lp:~lifeless/python-oops-datedir-repo/bug-971255
Merge into: lp:python-oops-datedir-repo
Diff against target: 590 lines (+26/-440)
7 files modified
NEWS (+5/-0)
README (+1/-4)
oops_datedir_repo/repository.py (+17/-30)
oops_datedir_repo/tests/__init__.py (+0/-1)
oops_datedir_repo/tests/test_repository.py (+3/-33)
oops_datedir_repo/tests/test_uniquefileallocator.py (+0/-160)
oops_datedir_repo/uniquefileallocator.py (+0/-212)
To merge this branch: bzr merge lp:~lifeless/python-oops-datedir-repo/bug-971255
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+126386@code.launchpad.net

Description of the change

UniqueFileAllocator lead to lots of race-condition related pain, we've had ages now for everyone to migrate, and the previous release is entirely usable for folk that might not be ready yet. Wake up, time to die.

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2012-09-19 02:21:51 +0000
3+++ NEWS 2012-09-26 06:41:19 +0000
4@@ -6,6 +6,11 @@
5 NEXT
6 ----
7
8+* The legacy uniquefileallocator code path has been dropped, fixing
9+ the bugs associated with its flawed design. As a result the
10+ instance_id parameter to DateDirRepo.__init__ has been dropped.
11+ (Robert Collins, #971255)
12+
13 0.0.19
14 ------
15
16
17=== modified file 'README'
18--- README 2011-11-11 04:48:53 +0000
19+++ README 2012-09-26 06:41:19 +0000
20@@ -53,14 +53,11 @@
21 functions : an OOPS report can be written to a disk file via the
22 serializer_rfc822.write() function, and read via the matching read() function.
23
24-The uniquefileallocator module is used by the repository implementation and
25-provides a system for allocating file names on disk.
26-
27 Typical usage::
28
29 >>> config = oops.Config()
30 >>> with fixtures.TempDir() as tempdir:
31- ... repo = oops_datedir_repo.DateDirRepo('/tmp/demo', 'servername')
32+ ... repo = oops_datedir_repo.DateDirRepo('/tmp/demo')
33 ... config.publishers.append(repo.publish)
34 ... ids = config.publish({'oops': '!!!'})
35
36
37=== modified file 'oops_datedir_repo/repository.py'
38--- oops_datedir_repo/repository.py 2012-09-03 23:13:20 +0000
39+++ oops_datedir_repo/repository.py 2012-09-26 06:41:19 +0000
40@@ -34,7 +34,6 @@
41 import anybson as bson
42 import serializer
43 import serializer_bson
44-from uniquefileallocator import UniqueFileAllocator
45
46
47 class DateDirRepo:
48@@ -50,21 +49,20 @@
49 more OOPS reports. OOPS file names can take various forms, but must not
50 end in .tmp - those are considered to be OOPS reports that are currently
51 being written.
52+
53+ * The behaviour of this class is to assign OOPS file names by hashing the
54+ serialized OOPS to get a unique file name. Other naming schemes are
55+ valid - the code doesn't assume anything other than the .tmp limitation
56+ above.
57 """
58
59- def __init__(self, error_dir, instance_id=None, serializer=None,
60- inherit_id=False, stash_path=False):
61+ def __init__(self, error_dir, serializer=None, inherit_id=False,
62+ stash_path=False):
63 """Create a DateDirRepo.
64
65 :param error_dir: The base directory to write OOPSes into. OOPSes are
66 written into a subdirectory this named after the date (e.g.
67 2011-12-30).
68- :param instance_id: If None, OOPS file names are named after the OOPS
69- id which is generated by hashing the serialized OOPS (without the
70- id field). Otherwise OOPS file names and ids are created by
71- allocating file names through a UniqueFileAllocator.
72- UniqueFileAllocator has significant performance and concurrency
73- limits and hash based naming is recommended.
74 :param serializer: If supplied should be the module (e.g.
75 oops_datedir_repo.serializer_rfc822) to use to serialize OOPSes.
76 Defaults to using serializer_bson.
77@@ -75,14 +73,6 @@
78 It is not stored in the OOPS written to disk, only the in-memory
79 model.
80 """
81- if instance_id is not None:
82- self.log_namer = UniqueFileAllocator(
83- output_root=error_dir,
84- log_type="OOPS",
85- log_subtype=instance_id,
86- )
87- else:
88- self.log_namer = None
89 self.root = error_dir
90 if serializer is None:
91 serializer = serializer_bson
92@@ -114,19 +104,16 @@
93 # Don't mess with the original report when changing ids etc.
94 original_report = report
95 report = dict(report)
96- if self.log_namer is not None:
97- oopsid, filename = self.log_namer.newId(now)
98- else:
99- md5hash = md5(serializer_bson.dumps(report)).hexdigest()
100- oopsid = 'OOPS-%s' % md5hash
101- prefix = os.path.join(self.root, now.strftime('%Y-%m-%d'))
102- if not os.path.isdir(prefix):
103- os.makedirs(prefix)
104- # For directories we need to set the x bits too.
105- os.chmod(
106- prefix, wanted_file_permission | stat.S_IXUSR | stat.S_IXGRP |
107- stat.S_IXOTH)
108- filename = os.path.join(prefix, oopsid)
109+ md5hash = md5(serializer_bson.dumps(report)).hexdigest()
110+ oopsid = 'OOPS-%s' % md5hash
111+ prefix = os.path.join(self.root, now.strftime('%Y-%m-%d'))
112+ if not os.path.isdir(prefix):
113+ os.makedirs(prefix)
114+ # For directories we need to set the x bits too.
115+ os.chmod(
116+ prefix, wanted_file_permission | stat.S_IXUSR | stat.S_IXGRP |
117+ stat.S_IXOTH)
118+ filename = os.path.join(prefix, oopsid)
119 if self.inherit_id:
120 oopsid = report.get('id') or oopsid
121 report['id'] = oopsid
122
123=== modified file 'oops_datedir_repo/tests/__init__.py'
124--- oops_datedir_repo/tests/__init__.py 2011-11-11 04:21:05 +0000
125+++ oops_datedir_repo/tests/__init__.py 2012-09-26 06:41:19 +0000
126@@ -21,7 +21,6 @@
127 def test_suite():
128 test_mod_names = [
129 'repository',
130- 'uniquefileallocator',
131 'serializer',
132 'serializer_bson',
133 'serializer_rfc822',
134
135=== modified file 'oops_datedir_repo/tests/test_repository.py'
136--- oops_datedir_repo/tests/test_repository.py 2012-09-03 21:04:20 +0000
137+++ oops_datedir_repo/tests/test_repository.py 2012-09-26 06:41:19 +0000
138@@ -38,7 +38,6 @@
139 DateDirRepo,
140 serializer_bson,
141 )
142-from oops_datedir_repo.uniquefileallocator import UniqueFileAllocator
143
144
145 class HasUnixPermissions:
146@@ -73,27 +72,6 @@
147
148 class TestDateDirRepo(testtools.TestCase):
149
150- def test_publish_permissions_lognamer(self):
151- errordir = self.useFixture(TempDir()).path
152- repo = DateDirRepo(errordir, 'T')
153- report = {'id': 'OOPS-91T1'}
154- now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=utc)
155-
156- # Set up default file creation mode to rwx------ as some restrictive
157- # servers do.
158- self.useFixture(UMaskFixture(stat.S_IRWXG | stat.S_IRWXO))
159- repo.publish(report, now)
160-
161- errorfile = os.path.join(repo.log_namer.output_dir(now), '01800.T1')
162- # Check errorfile and directory are set with the correct permission:
163- # rw-r--r-- and rwxr-xr-x accordingly
164- file_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
165- has_file_permission = HasUnixPermissions(file_perms)
166- has_dir_permission = HasUnixPermissions(
167- file_perms | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
168- self.assertThat(errorfile, has_file_permission)
169- self.assertThat(repo.log_namer.output_dir(now), has_dir_permission)
170-
171 def test_publish_permissions_hashnames(self):
172 repo = DateDirRepo(self.useFixture(TempDir()).path, stash_path=True)
173 report = {'id': 'OOPS-91T1'}
174@@ -112,23 +90,15 @@
175 self.assertThat(report['datedir_repo_filepath'], has_file_permission)
176 self.assertThat(repo.root + '/2006-04-01', has_dir_permission)
177
178- def test_sets_log_namer_to_a_UniqueFileAllocator(self):
179- repo = DateDirRepo(self.useFixture(TempDir()).path, 'T')
180- self.assertIsInstance(repo.log_namer, UniqueFileAllocator)
181-
182 def test_default_serializer_bson(self):
183- repo = DateDirRepo(self.useFixture(TempDir()).path, 'T')
184+ repo = DateDirRepo(self.useFixture(TempDir()).path)
185 self.assertEqual(serializer_bson, repo.serializer)
186
187 def test_settable_serializer(self):
188 an_object = object()
189- repo = DateDirRepo(self.useFixture(TempDir()).path, 'T', an_object)
190+ repo = DateDirRepo(self.useFixture(TempDir()).path, an_object)
191 self.assertEqual(an_object, repo.serializer)
192
193- def test_no_instance_id_no_log_namer(self):
194- repo = DateDirRepo(self.useFixture(TempDir()).path)
195- self.assertEqual(None, repo.log_namer)
196-
197 def test_publish_via_hash(self):
198 repo = DateDirRepo(self.useFixture(TempDir()).path)
199 now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=utc)
200@@ -178,7 +148,7 @@
201
202 def test_publish_existing_id_lognamer(self):
203 # The id reuse and file allocation strategies should be separate.
204- repo = DateDirRepo(self.useFixture(TempDir()).path, 'X',
205+ repo = DateDirRepo(self.useFixture(TempDir()).path,
206 inherit_id=True, stash_path=True)
207 now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=utc)
208 report = {'time': now, 'id': '45'}
209
210=== removed file 'oops_datedir_repo/tests/test_uniquefileallocator.py'
211--- oops_datedir_repo/tests/test_uniquefileallocator.py 2011-11-11 04:21:05 +0000
212+++ oops_datedir_repo/tests/test_uniquefileallocator.py 1970-01-01 00:00:00 +0000
213@@ -1,160 +0,0 @@
214-# Copyright (c) 2010, 2011, Canonical Ltd
215-#
216-# This program is free software: you can redistribute it and/or modify
217-# it under the terms of the GNU Lesser General Public License as published by
218-# the Free Software Foundation, version 3 only.
219-#
220-# This program is distributed in the hope that it will be useful,
221-# but WITHOUT ANY WARRANTY; without even the implied warranty of
222-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
223-# GNU Lesser General Public License for more details.
224-#
225-# You should have received a copy of the GNU Lesser General Public License
226-# along with this program. If not, see <http://www.gnu.org/licenses/>.
227-# GNU Lesser General Public License version 3 (see the file LICENSE).
228-
229-"""Tests for the unique file naming facility."""
230-
231-__metaclass__ = type
232-
233-import datetime
234-import os
235-import stat
236-
237-from fixtures import TempDir
238-import pytz
239-import testtools
240-
241-from oops_datedir_repo.uniquefileallocator import UniqueFileAllocator
242-
243-
244-UTC = pytz.timezone('UTC')
245-
246-
247-class TestUniqueFileAllocator(testtools.TestCase):
248-
249- def setUp(self):
250- super(TestUniqueFileAllocator, self).setUp()
251- tempdir = self.useFixture(TempDir())
252- self._tempdir = tempdir.path
253-
254- def test_setToken(self):
255- namer = UniqueFileAllocator("/any-old/path/", 'OOPS', 'T')
256- self.assertEqual('T', namer.get_log_infix())
257-
258- # Some scripts will append a string token to the prefix.
259- namer.setToken('CW')
260- self.assertEqual('TCW', namer.get_log_infix())
261-
262- # Some scripts run multiple processes and append a string number
263- # to the prefix.
264- namer.setToken('1')
265- self.assertEqual('T1', namer.get_log_infix())
266-
267- def assertUniqueFileAllocator(self, namer, now, expected_id,
268- expected_last_id, expected_suffix, expected_lastdir):
269- logid, filename = namer.newId(now)
270- self.assertEqual(logid, expected_id)
271- self.assertEqual(filename,
272- os.path.join(namer._output_root, expected_suffix))
273- self.assertEqual(namer._last_serial, expected_last_id)
274- self.assertEqual(namer._last_output_dir,
275- os.path.join(namer._output_root, expected_lastdir))
276-
277- def test_newId(self):
278- # TODO: This should return an id, fileobj instead of a file name, to
279- # reduce races with threads that are slow to use what they asked for,
280- # when combined with configuration changes causing disk scans. That
281- # would also permit using a completely stubbed out file system,
282- # reducing IO in tests that use UniqueFileAllocator (such as all the
283- # pagetests in Launchpad. At that point an interface to obtain a
284- # factory of UniqueFileAllocator's would be useful to parameterise the
285- # entire test suite.
286- namer = UniqueFileAllocator(self._tempdir, 'OOPS', 'T')
287- # first name of the day
288- self.assertUniqueFileAllocator(namer,
289- datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC),
290- 'OOPS-91T1', 1, '2006-04-01/01800.T1', '2006-04-01')
291- # second name of the day
292- self.assertUniqueFileAllocator(namer,
293- datetime.datetime(2006, 04, 01, 12, 00, 00, tzinfo=UTC),
294- 'OOPS-91T2', 2, '2006-04-01/43200.T2', '2006-04-01')
295-
296- # first name of the following day sets a new dir and the id starts
297- # over.
298- self.assertUniqueFileAllocator(namer,
299- datetime.datetime(2006, 04, 02, 00, 30, 00, tzinfo=UTC),
300- 'OOPS-92T1', 1, '2006-04-02/01800.T1', '2006-04-02')
301-
302- # Setting a token inserts the token into the filename.
303- namer.setToken('YYY')
304- logid, filename = namer.newId(
305- datetime.datetime(2006, 04, 02, 00, 30, 00, tzinfo=UTC))
306- self.assertEqual(logid, 'OOPS-92TYYY2')
307-
308- # Setting a type controls the log id:
309- namer.setToken('')
310- namer._log_type = "PROFILE"
311- logid, filename = namer.newId(
312- datetime.datetime(2006, 04, 02, 00, 30, 00, tzinfo=UTC))
313- self.assertEqual(logid, 'PROFILE-92T3')
314-
315- # Native timestamps are not permitted - UTC only.
316- now = datetime.datetime(2006, 04, 02, 00, 30, 00)
317- self.assertRaises(ValueError, namer.newId, now)
318-
319- def test_changeErrorDir(self):
320- """Test changing the log output dir."""
321- namer = UniqueFileAllocator(self._tempdir, 'OOPS', 'T')
322-
323- # First an id in the original error directory.
324- self.assertUniqueFileAllocator(namer,
325- datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC),
326- 'OOPS-91T1', 1, '2006-04-01/01800.T1', '2006-04-01')
327-
328- # UniqueFileAllocator uses the _output_root attribute to get the
329- # current output directory.
330- new_output_dir = self.useFixture(TempDir()).path
331- namer._output_root = new_output_dir
332-
333- # Now an id on the same day, in the new directory.
334- now = datetime.datetime(2006, 04, 01, 12, 00, 00, tzinfo=UTC)
335- log_id, filename = namer.newId(now)
336-
337- # Since it's a new directory, with no previous logs, the id is 1
338- # again, rather than 2.
339- self.assertEqual(log_id, 'OOPS-91T1')
340- self.assertEqual(namer._last_serial, 1)
341- self.assertEqual(namer._last_output_dir,
342- os.path.join(new_output_dir, '2006-04-01'))
343-
344- def test_findHighestSerial(self):
345- namer = UniqueFileAllocator(self._tempdir, "OOPS", "T")
346- # Creates the dir using now as the timestamp.
347- output_dir = namer.output_dir()
348- # write some files, in non-serial order.
349- open(os.path.join(output_dir, '12343.T1'), 'w').close()
350- open(os.path.join(output_dir, '12342.T2'), 'w').close()
351- open(os.path.join(output_dir, '12345.T3'), 'w').close()
352- open(os.path.join(output_dir, '1234567.T0010'), 'w').close()
353- open(os.path.join(output_dir, '12346.A42'), 'w').close()
354- open(os.path.join(output_dir, '12346.B100'), 'w').close()
355- # The namer should figure out the right highest serial.
356- self.assertEqual(namer._findHighestSerial(output_dir), 10)
357-
358- def test_output_dir_permission(self):
359- # Set up default dir creation mode to rwx------.
360- umask_permission = stat.S_IRWXG | stat.S_IRWXO
361- old_umask = os.umask(umask_permission)
362- namer = UniqueFileAllocator(self._tempdir, "OOPS", "T")
363- output_dir = namer.output_dir()
364- st = os.stat(output_dir)
365- # Permission we want here is: rwxr-xr-x
366- wanted_permission = (
367- stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH |
368- stat.S_IXOTH)
369- # Get only the permission bits for this directory.
370- dir_permission = stat.S_IMODE(st.st_mode)
371- self.assertEqual(dir_permission, wanted_permission)
372- # Restore the umask to the original value.
373- ignored = os.umask(old_umask)
374
375=== removed file 'oops_datedir_repo/uniquefileallocator.py'
376--- oops_datedir_repo/uniquefileallocator.py 2011-11-11 04:21:05 +0000
377+++ oops_datedir_repo/uniquefileallocator.py 1970-01-01 00:00:00 +0000
378@@ -1,212 +0,0 @@
379-# Copyright (c) 2010, 2011, Canonical Ltd
380-#
381-# This program is free software: you can redistribute it and/or modify
382-# it under the terms of the GNU Lesser General Public License as published by
383-# the Free Software Foundation, version 3 only.
384-#
385-# This program is distributed in the hope that it will be useful,
386-# but WITHOUT ANY WARRANTY; without even the implied warranty of
387-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
388-# GNU Lesser General Public License for more details.
389-#
390-# You should have received a copy of the GNU Lesser General Public License
391-# along with this program. If not, see <http://www.gnu.org/licenses/>.
392-# GNU Lesser General Public License version 3 (see the file LICENSE).
393-
394-
395-"""Create uniquely named log files on disk."""
396-
397-
398-__all__ = ['UniqueFileAllocator']
399-
400-__metaclass__ = type
401-
402-
403-import datetime
404-import errno
405-import os.path
406-import stat
407-import threading
408-
409-import pytz
410-
411-
412-UTC = pytz.utc
413-
414-# the section of the ID before the instance identifier is the
415-# days since the epoch, which is defined as the start of 2006.
416-epoch = datetime.datetime(2006, 01, 01, 00, 00, 00, tzinfo=UTC)
417-
418-
419-class UniqueFileAllocator:
420- """Assign unique file names to logs being written from an app/script.
421-
422- UniqueFileAllocator causes logs written from one process to be uniquely
423- named. It is not safe for use in multiple processes with the same output
424- root - each process must have a unique output root.
425- """
426-
427- def __init__(self, output_root, log_type, log_subtype):
428- """Create a UniqueFileAllocator.
429-
430- :param output_root: The root directory that logs should be placed in.
431- :param log_type: A string to use as a prefix in the ID assigned to new
432- logs. For instance, "OOPS".
433- :param log_subtype: A string to insert in the generate log filenames
434- between the day number and the serial. For instance "T" for
435- "Testing".
436- """
437- self._lock = threading.Lock()
438- self._output_root = output_root
439- self._last_serial = 0
440- self._last_output_dir = None
441- self._log_type = log_type
442- self._log_subtype = log_subtype
443- self._log_token = ""
444-
445- def _findHighestSerialFilename(self, directory=None, time=None):
446- """Find details of the last log present in the given directory.
447-
448- This function only considers logs with the currently
449- configured log_subtype.
450-
451- One of directory, time must be supplied.
452-
453- :param directory: Look in this directory.
454- :param time: Look in the directory that a log written at this time
455- would have been written to. If supplied, supercedes directory.
456- :return: a tuple (log_serial, log_filename), which will be (0,
457- None) if no logs are found. log_filename is a usable path, not
458- simply the basename.
459- """
460- if directory is None:
461- directory = self.output_dir(time)
462- prefix = self.get_log_infix()
463- lastid = 0
464- lastfilename = None
465- for filename in os.listdir(directory):
466- logid = filename.rsplit('.', 1)[1]
467- if not logid.startswith(prefix):
468- continue
469- logid = logid[len(prefix):]
470- if logid.isdigit() and (lastid is None or int(logid) > lastid):
471- lastid = int(logid)
472- lastfilename = filename
473- if lastfilename is not None:
474- lastfilename = os.path.join(directory, lastfilename)
475- return lastid, lastfilename
476-
477- def _findHighestSerial(self, directory):
478- """Find the last serial actually applied to disk in directory.
479-
480- The purpose of this function is to not repeat sequence numbers
481- if the logging application is restarted.
482-
483- This method is not thread safe, and only intended to be called
484- from the constructor (but it is called from other places in
485- integration tests).
486- """
487- return self._findHighestSerialFilename(directory)[0]
488-
489- def getFilename(self, log_serial, time):
490- """Get the filename for a given log serial and time."""
491- log_subtype = self.get_log_infix()
492- # TODO: Calling output_dir causes a global lock to be taken and a
493- # directory scan, which is bad for performance. It would be better
494- # to have a split out 'directory name for time' function which the
495- # 'want to use this directory now' function can call.
496- output_dir = self.output_dir(time)
497- second_in_day = time.hour * 3600 + time.minute * 60 + time.second
498- return os.path.join(
499- output_dir, '%05d.%s%s' % (
500- second_in_day, log_subtype, log_serial))
501-
502- def get_log_infix(self):
503- """Return the current log infix to use in ids and file names."""
504- return self._log_subtype + self._log_token
505-
506- def newId(self, now=None):
507- """Returns an (id, filename) pair for use by the caller.
508-
509- The ID is composed of a short string to identify the Launchpad
510- instance followed by an ID that is unique for the day.
511-
512- The filename is composed of the zero padded second in the day
513- followed by the ID. This ensures that reports are in date order when
514- sorted lexically.
515- """
516- if now is not None:
517- now = now.astimezone(UTC)
518- else:
519- now = datetime.datetime.now(UTC)
520- # We look up the error directory before allocating a new ID,
521- # because if the day has changed, errordir() will reset the ID
522- # counter to zero.
523- self.output_dir(now)
524- self._lock.acquire()
525- try:
526- self._last_serial += 1
527- newid = self._last_serial
528- finally:
529- self._lock.release()
530- subtype = self.get_log_infix()
531- day_number = (now - epoch).days + 1
532- log_id = '%s-%d%s%d' % (self._log_type, day_number, subtype, newid)
533- filename = self.getFilename(newid, now)
534- return log_id, filename
535-
536- def output_dir(self, now=None):
537- """Find or make the directory to allocate log names in.
538-
539- Log names are assigned within subdirectories containing the date the
540- assignment happened.
541- """
542- if now is not None:
543- now = now.astimezone(UTC)
544- else:
545- now = datetime.datetime.now(UTC)
546- date = now.strftime('%Y-%m-%d')
547- result = os.path.join(self._output_root, date)
548- if result != self._last_output_dir:
549- self._lock.acquire()
550- try:
551- self._last_output_dir = result
552- # make sure the directory exists
553- try:
554- os.makedirs(result)
555- except OSError, e:
556- if e.errno != errno.EEXIST:
557- raise
558- # Make sure the directory permission is set to: rwxr-xr-x
559- permission = (
560- stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
561- stat.S_IROTH | stat.S_IXOTH)
562- os.chmod(result, permission)
563- # TODO: Note that only one process can do this safely: its not
564- # cross-process safe, and also not entirely threadsafe:
565- # another # thread that has a new log and hasn't written it
566- # could then use that serial number. We should either make it
567- # really safe, or remove the contention entirely and log
568- # uniquely per thread of execution.
569- self._last_serial = self._findHighestSerial(result)
570- finally:
571- self._lock.release()
572- return result
573-
574- def listRecentReportFiles(self):
575- now = datetime.datetime.now(UTC)
576- yesterday = now - datetime.timedelta(days=1)
577- directories = [self.output_dir(now), self.output_dir(yesterday)]
578- for directory in directories:
579- report_names = os.listdir(directory)
580- for name in sorted(report_names, reverse=True):
581- yield directory, name
582-
583- def setToken(self, token):
584- """Append a string to the log subtype in filenames and log ids.
585-
586- :param token: a string to append..
587- Scripts that run multiple processes can use this to create a
588- unique identifier for each process.
589- """
590- self._log_token = token

Subscribers

People subscribed via source and target branches

to all changes: