Gentoo Archives: gentoo-commits

From: Mike Gilbert <floppym@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/portage:master commit in: bin/, lib/portage/util/, lib/portage/, lib/portage/_emirrordist/
Date: Mon, 01 Aug 2022 17:30:59
Message-Id: 1659375028.205d41608e55f78b620f8c3a0cb204c88df041ee.floppym@gentoo
1 commit: 205d41608e55f78b620f8c3a0cb204c88df041ee
2 Author: Mike Gilbert <floppym <AT> gentoo <DOT> org>
3 AuthorDate: Sat Jul 9 04:26:12 2022 +0000
4 Commit: Mike Gilbert <floppym <AT> gentoo <DOT> org>
5 CommitDate: Mon Aug 1 17:30:28 2022 +0000
6 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=205d4160
7
8 Avoid calling root-level logging functions
9
10 Instead, establish a named logger in each module. This will allow for
11 easier filtering of log messages if we ever expand the use of the
12 logging module.
13
14 Closes: https://github.com/gentoo/portage/pull/849
15 Signed-off-by: Mike Gilbert <floppym <AT> gentoo.org>
16
17 bin/doins.py | 8 +++--
18 lib/portage/_emirrordist/Config.py | 10 +++----
19 lib/portage/_emirrordist/ContentDB.py | 6 ++--
20 lib/portage/_emirrordist/DeletionIterator.py | 8 +++--
21 lib/portage/_emirrordist/DeletionTask.py | 28 +++++++++---------
22 lib/portage/_emirrordist/FetchTask.py | 44 +++++++++++++++-------------
23 lib/portage/_emirrordist/MirrorDistTask.py | 36 +++++++++++------------
24 lib/portage/eapi.py | 2 ++
25 lib/portage/util/shelve.py | 4 ++-
26 9 files changed, 79 insertions(+), 67 deletions(-)
27
28 diff --git a/bin/doins.py b/bin/doins.py
29 index 087756218..7905c5c0d 100644
30 --- a/bin/doins.py
31 +++ b/bin/doins.py
32 @@ -26,6 +26,8 @@ import sys
33 from portage.util import movefile
34 from portage.util.file_copy import copyfile
35
36 +logger = logging.getLogger("portage.bin.doins")
37 +
38
39 def _warn(helper, msg):
40 """Output warning message to stderr.
41 @@ -199,7 +201,7 @@ class _InsInProcessInstallRunner:
42 if self._parsed_options.preserve_timestamps:
43 _set_timestamps(sstat, dest)
44 except Exception:
45 - logging.exception(
46 + logger.exception(
47 "Failed to copy file: " "_parsed_options=%r, source=%r, dest_dir=%r",
48 self._parsed_options,
49 source,
50 @@ -383,7 +385,7 @@ class _InstallRunner:
51 except Exception:
52 if self._helpers_can_die:
53 raise
54 - logging.exception("install_dir failed.")
55 + logger.exception("install_dir failed.")
56
57
58 def _doins(opts, install_runner, relpath, source_root):
59 @@ -428,7 +430,7 @@ def _doins(opts, install_runner, relpath, source_root):
60 os.symlink(linkto, dest)
61 return True
62 except Exception:
63 - logging.exception(
64 + logger.exception(
65 "Failed to create symlink: " "opts=%r, relpath=%r, source_root=%r",
66 opts,
67 relpath,
68
69 diff --git a/lib/portage/_emirrordist/Config.py b/lib/portage/_emirrordist/Config.py
70 index 78b7a482a..734313c07 100644
71 --- a/lib/portage/_emirrordist/Config.py
72 +++ b/lib/portage/_emirrordist/Config.py
73 @@ -12,6 +12,8 @@ from portage.package.ebuild.fetch import MirrorLayoutConfig
74 from portage.util import grabdict, grablines
75 from .ContentDB import ContentDB
76
77 +logger = logging.getLogger(__name__)
78 +
79
80 class Config:
81 def __init__(self, options, portdb, event_loop):
82 @@ -89,13 +91,11 @@ class Config:
83 def _open_log(self, log_desc, log_path, mode):
84
85 if log_path is None or getattr(self.options, "dry_run", False):
86 - log_func = logging.info
87 + log_func = logger.info
88 line_format = "%s: %%s" % log_desc
89 add_newline = False
90 if log_path is not None:
91 - logging.warning(
92 - "dry-run: %s log " "redirected to logging.info" % log_desc
93 - )
94 + logger.warning("dry-run: %s log redirected to logging.info" % log_desc)
95 else:
96 self._open_files.append(io.open(log_path, mode=mode, encoding="utf_8"))
97 line_format = "%s\n"
98 @@ -137,7 +137,7 @@ class Config:
99 db = dbshelve.open(db_file, flags=open_flag)
100
101 if dry_run:
102 - logging.warning("dry-run: %s db opened in readonly mode" % db_desc)
103 + logger.warning("dry-run: %s db opened in readonly mode" % db_desc)
104 if not isinstance(db, dict):
105 volatile_db = dict((k, db[k]) for k in db)
106 db.close()
107
108 diff --git a/lib/portage/_emirrordist/ContentDB.py b/lib/portage/_emirrordist/ContentDB.py
109 index 6a5efbe95..ac6140257 100644
110 --- a/lib/portage/_emirrordist/ContentDB.py
111 +++ b/lib/portage/_emirrordist/ContentDB.py
112 @@ -8,6 +8,8 @@ import typing
113
114 from portage.package.ebuild.fetch import DistfileName
115
116 +logger = logging.getLogger(__name__)
117 +
118
119 class ContentDB:
120 """
121 @@ -110,10 +112,10 @@ class ContentDB:
122 pass
123
124 if remaining:
125 - logging.debug(("drop '%s' revision(s) from content db") % filename)
126 + logger.debug(("drop '%s' revision(s) from content db") % filename)
127 self._shelve[distfile_key] = remaining
128 else:
129 - logging.debug(("drop '%s' from content db") % filename)
130 + logger.debug(("drop '%s' from content db") % filename)
131 try:
132 del self._shelve[distfile_key]
133 except KeyError:
134
135 diff --git a/lib/portage/_emirrordist/DeletionIterator.py b/lib/portage/_emirrordist/DeletionIterator.py
136 index 59abce82f..6273ad281 100644
137 --- a/lib/portage/_emirrordist/DeletionIterator.py
138 +++ b/lib/portage/_emirrordist/DeletionIterator.py
139 @@ -9,6 +9,8 @@ from portage import os
140 from portage.package.ebuild.fetch import DistfileName
141 from .DeletionTask import DeletionTask
142
143 +logger = logging.getLogger(__name__)
144 +
145
146 class DeletionIterator:
147 def __init__(self, config):
148 @@ -61,7 +63,7 @@ class DeletionIterator:
149 break
150 else:
151 if exceptions:
152 - logging.error(
153 + logger.error(
154 "stat failed on '%s' in distfiles: %s\n"
155 % (filename, "; ".join(str(x) for x in exceptions))
156 )
157 @@ -103,7 +105,7 @@ class DeletionIterator:
158 deletion_entry = deletion_db.get(filename)
159
160 if deletion_entry is None:
161 - logging.debug("add '%s' to deletion db" % filename)
162 + logger.debug("add '%s' to deletion db" % filename)
163 deletion_db[filename] = start_time
164
165 elif deletion_entry + deletion_delay <= start_time:
166 @@ -123,4 +125,4 @@ class DeletionIterator:
167 except KeyError:
168 pass
169 else:
170 - logging.debug("drop '%s' from deletion db" % filename)
171 + logger.debug("drop '%s' from deletion db" % filename)
172
173 diff --git a/lib/portage/_emirrordist/DeletionTask.py b/lib/portage/_emirrordist/DeletionTask.py
174 index 7066e57a7..a5d96a9d9 100644
175 --- a/lib/portage/_emirrordist/DeletionTask.py
176 +++ b/lib/portage/_emirrordist/DeletionTask.py
177 @@ -9,6 +9,8 @@ from portage.package.ebuild.fetch import ContentHashLayout
178 from portage.util._async.FileCopier import FileCopier
179 from _emerge.CompositeTask import CompositeTask
180
181 +logger = logging.getLogger(__name__)
182 +
183
184 class DeletionTask(CompositeTask):
185
186 @@ -18,19 +20,17 @@ class DeletionTask(CompositeTask):
187 if self.config.options.recycle_dir is not None:
188 recycle_path = os.path.join(self.config.options.recycle_dir, self.distfile)
189 if self.config.options.dry_run:
190 - logging.info(
191 + logger.info(
192 ("dry-run: move '%s' from " "distfiles to recycle") % self.distfile
193 )
194 else:
195 - logging.debug(
196 - ("move '%s' from " "distfiles to recycle") % self.distfile
197 - )
198 + logger.debug(("move '%s' from " "distfiles to recycle") % self.distfile)
199 try:
200 # note: distfile_path can be a symlink here
201 os.rename(os.path.realpath(self.distfile_path), recycle_path)
202 except OSError as e:
203 if e.errno != errno.EXDEV:
204 - logging.error(
205 + logger.error(
206 ("rename %s from distfiles to " "recycle failed: %s")
207 % (self.distfile, e)
208 )
209 @@ -52,14 +52,14 @@ class DeletionTask(CompositeTask):
210 success = True
211
212 if self.config.options.dry_run:
213 - logging.info(("dry-run: delete '%s' from " "distfiles") % self.distfile)
214 + logger.info(("dry-run: delete '%s' from " "distfiles") % self.distfile)
215 else:
216 - logging.debug(("delete '%s' from " "distfiles") % self.distfile)
217 + logger.debug(("delete '%s' from " "distfiles") % self.distfile)
218 try:
219 os.unlink(self.distfile_path)
220 except OSError as e:
221 if e.errno not in (errno.ENOENT, errno.ESTALE):
222 - logging.error(
223 + logger.error(
224 "%s unlink failed in distfiles: %s" % (self.distfile, e)
225 )
226 success = False
227 @@ -85,13 +85,13 @@ class DeletionTask(CompositeTask):
228 os.unlink(copier.src_path)
229 except OSError as e:
230 if e.errno not in (errno.ENOENT, errno.ESTALE):
231 - logging.error(
232 + logger.error(
233 "%s unlink failed in distfiles: %s" % (self.distfile, e)
234 )
235 success = False
236
237 else:
238 - logging.error(
239 + logger.error(
240 ("%s copy from distfiles " "to recycle failed: %s")
241 % (self.distfile, copier.future.exception())
242 )
243 @@ -109,7 +109,7 @@ class DeletionTask(CompositeTask):
244 success = True
245 for layout in self.config.layouts:
246 if isinstance(layout, ContentHashLayout) and not self.distfile.digests:
247 - logging.debug(("_delete_links: '%s' has " "no digests") % self.distfile)
248 + logger.debug(("_delete_links: '%s' has " "no digests") % self.distfile)
249 continue
250 distfile_path = os.path.join(
251 self.config.options.distfiles, layout.get_path(self.distfile)
252 @@ -118,7 +118,7 @@ class DeletionTask(CompositeTask):
253 os.unlink(distfile_path)
254 except OSError as e:
255 if e.errno not in (errno.ENOENT, errno.ESTALE):
256 - logging.error(
257 + logger.error(
258 "%s unlink failed in distfiles: %s" % (self.distfile, e)
259 )
260 success = False
261 @@ -144,7 +144,7 @@ class DeletionTask(CompositeTask):
262 except KeyError:
263 pass
264 else:
265 - logging.debug(("drop '%s' from " "distfiles db") % self.distfile)
266 + logger.debug(("drop '%s' from " "distfiles db") % self.distfile)
267
268 if self.config.content_db is not None:
269 self.config.content_db.remove(self.distfile)
270 @@ -155,4 +155,4 @@ class DeletionTask(CompositeTask):
271 except KeyError:
272 pass
273 else:
274 - logging.debug(("drop '%s' from " "deletion db") % self.distfile)
275 + logger.debug(("drop '%s' from " "deletion db") % self.distfile)
276
277 diff --git a/lib/portage/_emirrordist/FetchTask.py b/lib/portage/_emirrordist/FetchTask.py
278 index 304837332..89fd1657e 100644
279 --- a/lib/portage/_emirrordist/FetchTask.py
280 +++ b/lib/portage/_emirrordist/FetchTask.py
281 @@ -17,6 +17,8 @@ from portage.util._async.PipeLogger import PipeLogger
282 from portage.util._async.PopenProcess import PopenProcess
283 from _emerge.CompositeTask import CompositeTask
284
285 +logger = logging.getLogger(__name__)
286 +
287 default_hash_name = portage.const.MANIFEST2_HASH_DEFAULT
288
289 # Use --no-check-certificate since Manifest digests should provide
290 @@ -91,7 +93,7 @@ class FetchTask(CompositeTask):
291 self.scheduler.output(
292 msg + "\n", background=True, log_path=self._log_path
293 )
294 - logging.error(msg)
295 + logger.error(msg)
296 else:
297 break
298
299 @@ -100,7 +102,7 @@ class FetchTask(CompositeTask):
300 if not size_ok:
301 if self.config.options.dry_run:
302 if st is not None:
303 - logging.info(
304 + logger.info(
305 ("dry-run: delete '%s' with " "wrong size from distfiles")
306 % (self.distfile,)
307 )
308 @@ -115,7 +117,7 @@ class FetchTask(CompositeTask):
309 )
310 if self._unlink_file(unlink_path, "distfiles"):
311 if st is not None:
312 - logging.debug(
313 + logger.debug(
314 ("delete '%s' with " "wrong size from distfiles")
315 % (self.distfile,)
316 )
317 @@ -172,14 +174,14 @@ class FetchTask(CompositeTask):
318
319 if self.config.options.dry_run:
320 if os.path.exists(recycle_file):
321 - logging.info("dry-run: delete '%s' from recycle" % (self.distfile,))
322 + logger.info("dry-run: delete '%s' from recycle" % (self.distfile,))
323 else:
324 try:
325 os.unlink(recycle_file)
326 except OSError:
327 pass
328 else:
329 - logging.debug("delete '%s' from recycle" % (self.distfile,))
330 + logger.debug("delete '%s' from recycle" % (self.distfile,))
331
332 def _distfiles_digester_exit(self, digester):
333
334 @@ -195,7 +197,7 @@ class FetchTask(CompositeTask):
335 # from the administrator.
336 msg = "%s distfiles digester failed unexpectedly" % (self.distfile,)
337 self.scheduler.output(msg + "\n", background=True, log_path=self._log_path)
338 - logging.error(msg)
339 + logger.error(msg)
340 self.config.log_failure("%s\t%s\t%s" % (self.cpv, self.distfile, msg))
341 self.config.file_failures[self.distfile] = self.cpv
342 self.wait()
343 @@ -312,7 +314,7 @@ class FetchTask(CompositeTask):
344 self.scheduler.output(
345 msg + "\n", background=True, log_path=self._log_path
346 )
347 - logging.error(msg)
348 + logger.error(msg)
349 else:
350 size_ok = st.st_size == self.digests["size"]
351 self._current_stat = st
352 @@ -345,7 +347,7 @@ class FetchTask(CompositeTask):
353 current_mirror.name,
354 )
355 self.scheduler.output(msg + "\n", background=True, log_path=self._log_path)
356 - logging.error(msg)
357 + logger.error(msg)
358 else:
359 bad_digest = self._find_bad_digest(digester.digests)
360 if bad_digest is not None:
361 @@ -359,18 +361,18 @@ class FetchTask(CompositeTask):
362 self.scheduler.output(
363 msg + "\n", background=True, log_path=self._log_path
364 )
365 - logging.error(msg)
366 + logger.error(msg)
367 elif self.config.options.dry_run:
368 # Report success without actually touching any files
369 if self._same_device(
370 current_mirror.location, self.config.options.distfiles
371 ):
372 - logging.info(
373 + logger.info(
374 ("dry-run: hardlink '%s' from %s " "to distfiles")
375 % (self.distfile, current_mirror.name)
376 )
377 else:
378 - logging.info(
379 + logger.info(
380 "dry-run: copy '%s' from %s to distfiles"
381 % (self.distfile, current_mirror.name)
382 )
383 @@ -387,7 +389,7 @@ class FetchTask(CompositeTask):
384 if self._hardlink_atomic(
385 src, dest, "%s to %s" % (current_mirror.name, "distfiles")
386 ):
387 - logging.debug(
388 + logger.debug(
389 "hardlink '%s' from %s to distfiles"
390 % (self.distfile, current_mirror.name)
391 )
392 @@ -424,10 +426,10 @@ class FetchTask(CompositeTask):
393 copier.future.exception(),
394 )
395 self.scheduler.output(msg + "\n", background=True, log_path=self._log_path)
396 - logging.error(msg)
397 + logger.error(msg)
398 else:
399
400 - logging.debug(
401 + logger.debug(
402 "copy '%s' from %s to distfiles" % (self.distfile, current_mirror.name)
403 )
404
405 @@ -447,7 +449,7 @@ class FetchTask(CompositeTask):
406 self.scheduler.output(
407 msg + "\n", background=True, log_path=self._log_path
408 )
409 - logging.error(msg)
410 + logger.error(msg)
411
412 self._success()
413 self.returncode = os.EX_OK
414 @@ -460,7 +462,7 @@ class FetchTask(CompositeTask):
415
416 if self.config.options.dry_run:
417 # Simply report success.
418 - logging.info("dry-run: fetch '%s' from '%s'" % (self.distfile, uri))
419 + logger.info("dry-run: fetch '%s' from '%s'" % (self.distfile, uri))
420 self._success()
421 self.returncode = os.EX_OK
422 self._async_wait()
423 @@ -543,7 +545,7 @@ class FetchTask(CompositeTask):
424 self._fetch_tmp_dir_info,
425 )
426 self.scheduler.output(msg + "\n", background=True, log_path=self._log_path)
427 - logging.error(msg)
428 + logger.error(msg)
429 else:
430 bad_digest = self._find_bad_digest(digester.digests)
431 if bad_digest is not None:
432 @@ -608,7 +610,7 @@ class FetchTask(CompositeTask):
433 copier.future.exception(),
434 )
435 self.scheduler.output(msg + "\n", background=True, log_path=self._log_path)
436 - logging.error(msg)
437 + logger.error(msg)
438 self.config.log_failure("%s\t%s\t%s" % (self.cpv, self.distfile, msg))
439 self.config.file_failures[self.distfile] = self.cpv
440 self.returncode = 1
441 @@ -662,7 +664,7 @@ class FetchTask(CompositeTask):
442 self.scheduler.output(
443 msg + "\n", background=True, log_path=self._log_path
444 )
445 - logging.error(msg)
446 + logger.error(msg)
447 return False
448 return True
449
450 @@ -720,7 +722,7 @@ class FetchTask(CompositeTask):
451 self.scheduler.output(
452 msg + "\n", background=True, log_path=self._log_path
453 )
454 - logging.error(msg)
455 + logger.error(msg)
456 return False
457
458 try:
459 @@ -734,7 +736,7 @@ class FetchTask(CompositeTask):
460 self.scheduler.output(
461 msg + "\n", background=True, log_path=self._log_path
462 )
463 - logging.error(msg)
464 + logger.error(msg)
465 return False
466 finally:
467 try:
468
469 diff --git a/lib/portage/_emirrordist/MirrorDistTask.py b/lib/portage/_emirrordist/MirrorDistTask.py
470 index 28164e645..9423125df 100644
471 --- a/lib/portage/_emirrordist/MirrorDistTask.py
472 +++ b/lib/portage/_emirrordist/MirrorDistTask.py
473 @@ -17,6 +17,8 @@ from _emerge.CompositeTask import CompositeTask
474 from .FetchIterator import FetchIterator
475 from .DeletionIterator import DeletionIterator
476
477 +logger = logging.getLogger(__name__)
478 +
479
480 class MirrorDistTask(CompositeTask):
481
482 @@ -99,14 +101,14 @@ class MirrorDistTask(CompositeTask):
483 st = os.stat(recycle_file)
484 except OSError as e:
485 if e.errno not in (errno.ENOENT, errno.ESTALE):
486 - logging.error(
487 + logger.error(
488 ("stat failed for '%s' in " "recycle: %s") % (filename, e)
489 )
490 continue
491
492 value = recycle_db_cache.pop(filename, None)
493 if value is None:
494 - logging.debug(("add '%s' to " "recycle db") % filename)
495 + logger.debug(("add '%s' to " "recycle db") % filename)
496 recycle_db[filename] = (st.st_size, start_time)
497 else:
498 r_size, r_time = value
499 @@ -114,27 +116,25 @@ class MirrorDistTask(CompositeTask):
500 recycle_db[filename] = (st.st_size, start_time)
501 elif r_time + r_deletion_delay < start_time:
502 if self._config.options.dry_run:
503 - logging.info(
504 - ("dry-run: delete '%s' from " "recycle") % filename
505 - )
506 - logging.info(("drop '%s' from " "recycle db") % filename)
507 + logger.info(("dry-run: delete '%s' from " "recycle") % filename)
508 + logger.info(("drop '%s' from " "recycle db") % filename)
509 else:
510 try:
511 os.unlink(recycle_file)
512 except OSError as e:
513 if e.errno not in (errno.ENOENT, errno.ESTALE):
514 - logging.error(
515 + logger.error(
516 ("delete '%s' from " "recycle failed: %s")
517 % (filename, e)
518 )
519 else:
520 - logging.debug(("delete '%s' from " "recycle") % filename)
521 + logger.debug(("delete '%s' from " "recycle") % filename)
522 try:
523 del recycle_db[filename]
524 except KeyError:
525 pass
526 else:
527 - logging.debug(
528 + logger.debug(
529 ("drop '%s' from " "recycle db") % filename
530 )
531
532 @@ -147,7 +147,7 @@ class MirrorDistTask(CompositeTask):
533 except KeyError:
534 pass
535 else:
536 - logging.debug(("drop non-existent '%s' from " "recycle db") % filename)
537 + logger.debug(("drop non-existent '%s' from " "recycle db") % filename)
538
539 def _scheduled_deletion_log(self):
540
541 @@ -169,7 +169,7 @@ class MirrorDistTask(CompositeTask):
542 date_files.append(filename)
543
544 if dry_run:
545 - logging.warning(
546 + logger.warning(
547 "dry-run: scheduled-deletions log "
548 "will be summarized via logging.info"
549 )
550 @@ -178,7 +178,7 @@ class MirrorDistTask(CompositeTask):
551 for date in sorted(date_map):
552 date_files = date_map[date]
553 if dry_run:
554 - logging.info(
555 + logger.info(
556 ("dry-run: scheduled deletions for %s: %s files")
557 % (date, len(date_files))
558 )
559 @@ -202,12 +202,12 @@ class MirrorDistTask(CompositeTask):
560 added_file_count = self._config.added_file_count
561 added_byte_count = self._config.added_byte_count
562
563 - logging.info("finished in %i seconds" % elapsed_time)
564 - logging.info("failed to fetch %i files" % fail_count)
565 - logging.info("deleted %i files" % delete_count)
566 - logging.info("deletion of %i files scheduled" % scheduled_deletion_count)
567 - logging.info("added %i files" % added_file_count)
568 - logging.info("added %i bytes total" % added_byte_count)
569 + logger.info("finished in %i seconds" % elapsed_time)
570 + logger.info("failed to fetch %i files" % fail_count)
571 + logger.info("deleted %i files" % delete_count)
572 + logger.info("deletion of %i files scheduled" % scheduled_deletion_count)
573 + logger.info("added %i files" % added_file_count)
574 + logger.info("added %i bytes total" % added_byte_count)
575
576 def _cleanup(self):
577 """
578
579 diff --git a/lib/portage/eapi.py b/lib/portage/eapi.py
580 index 2c1701870..969cf9027 100644
581 --- a/lib/portage/eapi.py
582 +++ b/lib/portage/eapi.py
583 @@ -7,6 +7,8 @@ from typing import Optional
584
585 from portage import eapi_is_supported
586
587 +logger = logging.getLogger(__name__)
588 +
589
590 def eapi_has_iuse_defaults(eapi: str) -> bool:
591 return _get_eapi_attrs(eapi).iuse_defaults
592
593 diff --git a/lib/portage/util/shelve.py b/lib/portage/util/shelve.py
594 index 6100c8719..85d5829f3 100644
595 --- a/lib/portage/util/shelve.py
596 +++ b/lib/portage/util/shelve.py
597 @@ -5,6 +5,8 @@ import logging
598 import pickle
599 import shelve
600
601 +logger = logging.getLogger(__name__)
602 +
603
604 def open_shelve(db_file, flag="r"):
605 """
606 @@ -36,7 +38,7 @@ def dump(args):
607 try:
608 value = src[key]
609 except KeyError:
610 - logging.exception(key)
611 + logger.exception(key)
612 continue
613 pickle.dump((key, value), dest)
614 finally: