Merge lp:~aptdaemon-developers/aptdaemon/threading into lp:aptdaemon

Proposed by Sebastian Heinlein
Status: Merged
Merged at revision: 701
Proposed branch: lp:~aptdaemon-developers/aptdaemon/threading
Merge into: lp:aptdaemon
Diff against target: 970 lines (+252/-111)
6 files modified
NEWS (+2/-0)
aptdaemon/core.py (+182/-43)
aptdaemon/loop.py (+0/-32)
aptdaemon/progress.py (+8/-14)
aptdaemon/utils.py (+30/-0)
aptdaemon/worker.py (+30/-22)
To merge this branch: bzr merge lp:~aptdaemon-developers/aptdaemon/threading
Reviewer Review Type Date Requested Status
Aptdaemon Developers Pending
Review via email: mp+77802@code.launchpad.net

Description of the change

Moves the worker and the simulate actions to separate threads. This makes the daemon more responsive and allows to easily queue simulate and the comming PK query methods.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2011-08-27 11:37:42 +0000
+++ NEWS 2011-10-02 05:32:23 +0000
@@ -2,6 +2,8 @@
22
3 * Enhancements:3 * Enhancements:
44
5 - Use threading internally instead for the worker
6
5 - Support multiarch package names like apt-get, e.g. xterm:amd647 - Support multiarch package names like apt-get, e.g. xterm:amd64
68
7 - Store the transaction role and sender in apt's history log9 - Store the transaction role and sender in apt's history log
810
=== modified file 'aptdaemon/core.py'
--- aptdaemon/core.py 2011-09-15 14:23:50 +0000
+++ aptdaemon/core.py 2011-10-02 05:32:23 +0000
@@ -47,24 +47,27 @@
47import signal47import signal
48import sys48import sys
49import time49import time
50import threading
50import uuid51import uuid
5152
53from defer import inline_callbacks, return_value
54from defer.utils import dbus_deferred_method
52import gobject55import gobject
56gobject.threads_init()
53import dbus.exceptions57import dbus.exceptions
54import dbus.service58import dbus.service
55import dbus.mainloop.glib59import dbus.mainloop.glib
56import dbus.glib60dbus.mainloop.glib.threads_init()
57from softwareproperties.AptAuth import AptAuth61from softwareproperties.AptAuth import AptAuth
58import apt_pkg62import apt_pkg
5963
60from config import ConfigWriter64from config import ConfigWriter
61import errors65import errors
62import enums66import enums
63from defer import inline_callbacks, return_value
64from defer.utils import dbus_deferred_method
65import policykit167import policykit1
66from worker import AptWorker, DummyWorker68from worker import AptWorker, DummyWorker
67from loop import mainloop69from utils import locked
70
6871
69# Setup i18n72# Setup i18n
70_ = lambda msg: gettext.dgettext("aptdaemon", msg)73_ = lambda msg: gettext.dgettext("aptdaemon", msg)
@@ -313,6 +316,7 @@
313 connect -- if the Transaction should connect to DBus (default is True)316 connect -- if the Transaction should connect to DBus (default is True)
314 bus -- the DBus connection which should be used (defaults to system bus)317 bus -- the DBus connection which should be used (defaults to system bus)
315 """318 """
319 self.lock = threading.Lock()
316 tid = uuid.uuid4().get_hex()320 tid = uuid.uuid4().get_hex()
317 self.tid = "/org/debian/apt/transaction/%s" % tid321 self.tid = "/org/debian/apt/transaction/%s" % tid
318 if connect == True:322 if connect == True:
@@ -330,18 +334,20 @@
330 packages = ([], [], [], [], [], [])334 packages = ([], [], [], [], [], [])
331 if not kwargs:335 if not kwargs:
332 kwargs = {}336 kwargs = {}
337 self.sender = sender
333 self.queue = queue338 self.queue = queue
334 self.uid = uid339 self.uid = uid
335 self.locale = dbus.String("")340 self.kwargs = kwargs
336 self.allow_unauthenticated = dbus.Boolean(False)341 # Mutable properties which need to be protected by a lock
337 self.remove_obsoleted_depends = dbus.Boolean(False)342 self._locale = dbus.String("")
343 self._allow_unauthenticated = dbus.Boolean(False)
344 self._remove_obsoleted_depends = dbus.Boolean(False)
338 self.http_proxy = dbus.String("")345 self.http_proxy = dbus.String("")
339 self.terminal = dbus.String("")346 self._terminal = dbus.String("")
340 self.debconf = dbus.String("")347 self._debconf = dbus.String("")
341 self.kwargs = kwargs
342 self._translation = None348 self._translation = None
343 # The transaction which should be executed after this one349 # The transaction which should be executed after this one
344 self.after = None350 self._after = None
345 self._role = dbus.String(role)351 self._role = dbus.String(role)
346 self._progress = dbus.Int32(0)352 self._progress = dbus.Int32(0)
347 # items_done, total_items, bytes_done, total_bytes, speed, time353 # items_done, total_items, bytes_done, total_bytes, speed, time
@@ -359,8 +365,9 @@
359 self._required_medium = dbus.Struct(("", ""), signature="ss")365 self._required_medium = dbus.Struct(("", ""), signature="ss")
360 self._config_file_conflict = dbus.Struct(("", ""), signature="ss")366 self._config_file_conflict = dbus.Struct(("", ""), signature="ss")
361 self._config_file_conflict_resolution = ""367 self._config_file_conflict_resolution = ""
362 self.cancelled = dbus.Boolean(False)368 self._cancelled = dbus.Boolean(False)
363 self.paused = dbus.Boolean(False)369 self._paused = dbus.Boolean(False)
370 self.feedback = threading.Event()
364 self._meta_data = dbus.Dictionary(signature="sv")371 self._meta_data = dbus.Dictionary(signature="sv")
365 self._download = dbus.Int64(0)372 self._download = dbus.Int64(0)
366 self._space = dbus.Int64(0)373 self._space = dbus.Int64(0)
@@ -376,15 +383,64 @@
376 self._idle_watch = gobject.timeout_add_seconds(383 self._idle_watch = gobject.timeout_add_seconds(
377 TRANSACTION_IDLE_TIMEOUT, self._remove_from_connection_no_raise)384 TRANSACTION_IDLE_TIMEOUT, self._remove_from_connection_no_raise)
378 # Handle a disconnect of the client application385 # Handle a disconnect of the client application
379 self.sender_alive = True386 self._sender_alive = True
380 if bus:387 if bus:
381 self._sender_watch = bus.watch_name_owner(sender,388 self._sender_watch = bus.watch_name_owner(sender,
382 self._sender_owner_changed)389 self._sender_owner_changed)
383 else:390 else:
384 self._sender_watch = None391 self._sender_watch = None
385 self.sender = sender392 self._output = ""
386 self.output = ""393 self._simulated = None
387 self.simulated = None394
395 @locked
396 def _get_cancelled(self):
397 return self._cancelled
398
399 @locked
400 def _set_cancelled(self, txt):
401 self._cancelled = txt
402
403 cancelled = property(_get_cancelled, _set_cancelled)
404
405 @locked
406 def _get_paused(self):
407 return self._paused
408
409 @locked
410 def _set_paused(self, bool):
411 self._paused = bool
412
413 paused = property(_get_paused, _set_paused)
414
415 @locked
416 def _get_simulated(self):
417 return self._simulated
418
419 @locked
420 def _set_simulated(self, txt):
421 self._simulated = txt
422
423 simulated = property(_get_simulated, _set_simulated)
424
425 @locked
426 def _get_output(self):
427 return self._output
428
429 @locked
430 def _set_output(self, txt):
431 self._output = txt
432
433 output = property(_get_output, _set_output)
434
435 @locked
436 def _get_sender_alive(self):
437 return self._sender_alive
438
439 @locked
440 def _set_sender_alive(self, alive):
441 self._sender_alive = alive
442
443 sender_alive = property(_get_sender_alive, _set_sender_alive)
388444
389 def _sender_owner_changed(self, connection):445 def _sender_owner_changed(self, connection):
390 """Callback if the owner of the original sender changed, e.g.446 """Callback if the owner of the original sender changed, e.g.
@@ -458,9 +514,11 @@
458 raise errors.InvalidMetaDataError("The value has to be a "514 raise errors.InvalidMetaDataError("The value has to be a "
459 "string: %s" % value)515 "string: %s" % value)
460 # Merge new data into existing one:516 # Merge new data into existing one:
461 self._meta_data.update(data)517 with self.lock:
462 self.PropertyChanged("MetaData", self._meta_data)518 self._meta_data.update(data)
519 self.PropertyChanged("MetaData", self._meta_data)
463520
521 @locked
464 def _get_meta_data(self):522 def _get_meta_data(self):
465 return self._meta_data523 return self._meta_data
466524
@@ -468,22 +526,26 @@
468 doc="Allows client applications to store meta data "526 doc="Allows client applications to store meta data "
469 "for the transaction in a dictionary.")527 "for the transaction in a dictionary.")
470528
529 @locked
471 def _set_role(self, enum):530 def _set_role(self, enum):
472 if self._role != enums.ROLE_UNSET:531 if self._role != enums.ROLE_UNSET:
473 raise errors.TransactionRoleAlreadySet()532 raise errors.TransactionRoleAlreadySet()
474 self._role = dbus.String(enum)533 self._role = dbus.String(enum)
475 self.PropertyChanged("Role", self._role)534 self.PropertyChanged("Role", self._role)
476535
536 @locked
477 def _get_role(self):537 def _get_role(self):
478 return self._role538 return self._role
479539
480 role = property(_get_role, _set_role, doc="Operation type of transaction.")540 role = property(_get_role, _set_role, doc="Operation type of transaction.")
481541
542 @locked
482 def _set_progress_details(self, details):543 def _set_progress_details(self, details):
483 # items_done, total_items, bytes_done, total_bytes, speed, time544 # items_done, total_items, bytes_done, total_bytes, speed, time
484 self._progress_details = self._convert_struct(details, "iixxdx")545 self._progress_details = self._convert_struct(details, "iixxdx")
485 self.PropertyChanged("ProgressDetails", self._progress_details)546 self.PropertyChanged("ProgressDetails", self._progress_details)
486547
548 @locked
487 def _get_progress_details(self):549 def _get_progress_details(self):
488 return self._progress_details550 return self._progress_details
489551
@@ -493,12 +555,14 @@
493 "bytes done, total bytes, speed and "555 "bytes done, total bytes, speed and "
494 "remaining time")556 "remaining time")
495557
558 @locked
496 def _set_error(self, excep):559 def _set_error(self, excep):
497 self._error = excep560 self._error = excep
498 msg = self.gettext(excep.details) % excep.details_args561 msg = self.gettext(excep.details) % excep.details_args
499 self._error_property = self._convert_struct((excep.code, msg), "ss")562 self._error_property = self._convert_struct((excep.code, msg), "ss")
500 self.PropertyChanged("Error", self._error_property)563 self.PropertyChanged("Error", self._error_property)
501564
565 @locked
502 def _get_error(self):566 def _get_error(self):
503 return self._error567 return self._error
504568
@@ -506,9 +570,10 @@
506570
507 def _set_exit(self, enum):571 def _set_exit(self, enum):
508 self.status = enums.STATUS_FINISHED572 self.status = enums.STATUS_FINISHED
509 self._exit = dbus.String(enum)573 with self.lock:
510 self.PropertyChanged("ExitState", self._exit)574 self._exit = dbus.String(enum)
511 self.Finished(self._exit)575 self.PropertyChanged("ExitState", self._exit)
576 self.Finished(self._exit)
512 if self._sender_watch:577 if self._sender_watch:
513 self._sender_watch.cancel()578 self._sender_watch.cancel()
514 # Remove the transaction from the Bus after it is complete. A short579 # Remove the transaction from the Bus after it is complete. A short
@@ -516,15 +581,18 @@
516 gobject.timeout_add_seconds(TRANSACTION_DEL_TIMEOUT,581 gobject.timeout_add_seconds(TRANSACTION_DEL_TIMEOUT,
517 self._remove_from_connection_no_raise)582 self._remove_from_connection_no_raise)
518583
584 @locked
519 def _get_exit(self):585 def _get_exit(self):
520 return self._exit586 return self._exit
521587
522 exit = property(_get_exit, _set_exit,588 exit = property(_get_exit, _set_exit,
523 doc="The exit state of the transaction.")589 doc="The exit state of the transaction.")
524590
591 @locked
525 def _get_download(self):592 def _get_download(self):
526 return self._download593 return self._download
527594
595 @locked
528 def _set_download(self, size):596 def _set_download(self, size):
529 self._download = dbus.Int64(size)597 self._download = dbus.Int64(size)
530 self.PropertyChanged("Download", self._download)598 self.PropertyChanged("Download", self._download)
@@ -532,9 +600,11 @@
532 download = property(_get_download, _set_download,600 download = property(_get_download, _set_download,
533 doc="The download size of the transaction.")601 doc="The download size of the transaction.")
534602
603 @locked
535 def _get_space(self):604 def _get_space(self):
536 return self._space605 return self._space
537606
607 @locked
538 def _set_space(self, size):608 def _set_space(self, size):
539 self._space = dbus.Int64(size)609 self._space = dbus.Int64(size)
540 self.PropertyChanged("Space", self._space)610 self.PropertyChanged("Space", self._space)
@@ -542,12 +612,14 @@
542 space = property(_get_space, _set_space,612 space = property(_get_space, _set_space,
543 doc="The required disk space of the transaction.")613 doc="The required disk space of the transaction.")
544614
615 @locked
545 def _set_packages(self, packages):616 def _set_packages(self, packages):
546 self._packages = dbus.Struct([dbus.Array(pkgs, signature="s")617 self._packages = dbus.Struct([dbus.Array(pkgs, signature="s")
547 for pkgs in packages],618 for pkgs in packages],
548 signature="as")619 signature="as")
549 self.PropertyChanged("Packages", self._packages)620 self.PropertyChanged("Packages", self._packages)
550621
622 @locked
551 def _get_packages(self):623 def _get_packages(self):
552 return self._packages624 return self._packages
553625
@@ -556,9 +628,11 @@
556 "reinstalled, removed, purged, upgraded or "628 "reinstalled, removed, purged, upgraded or "
557 "downgraded.")629 "downgraded.")
558630
631 @locked
559 def _get_unauthenticated(self):632 def _get_unauthenticated(self):
560 return self._unauthenticated633 return self._unauthenticated
561634
635 @locked
562 def _set_unauthenticated(self, unauthenticated):636 def _set_unauthenticated(self, unauthenticated):
563 self._unauthenticated = dbus.Array(unauthenticated, signature="s")637 self._unauthenticated = dbus.Array(unauthenticated, signature="s")
564 self.PropertyChanged("Unauthenticated", self._unauthenticated)638 self.PropertyChanged("Unauthenticated", self._unauthenticated)
@@ -567,9 +641,11 @@
567 doc="Unauthenticated packages in this "641 doc="Unauthenticated packages in this "
568 "transaction")642 "transaction")
569643
644 @locked
570 def _get_depends(self):645 def _get_depends(self):
571 return self._depends646 return self._depends
572647
648 @locked
573 def _set_depends(self, depends):649 def _set_depends(self, depends):
574 self._depends = dbus.Struct([dbus.Array(deps, signature="s")650 self._depends = dbus.Struct([dbus.Array(deps, signature="s")
575 for deps in depends],651 for deps in depends],
@@ -580,9 +656,11 @@
580 doc="The additional dependencies: installs, removals, "656 doc="The additional dependencies: installs, removals, "
581 "upgrades and downgrades.")657 "upgrades and downgrades.")
582658
659 @locked
583 def _get_status(self):660 def _get_status(self):
584 return self._status661 return self._status
585662
663 @locked
586 def _set_status(self, enum):664 def _set_status(self, enum):
587 self._status = dbus.String(enum)665 self._status = dbus.String(enum)
588 self.PropertyChanged("Status", self._status)666 self.PropertyChanged("Status", self._status)
@@ -590,9 +668,11 @@
590 status = property(_get_status, _set_status,668 status = property(_get_status, _set_status,
591 doc="The status of the transaction.")669 doc="The status of the transaction.")
592670
671 @locked
593 def _get_status_details(self):672 def _get_status_details(self):
594 return self._status_details673 return self._status_details
595674
675 @locked
596 def _set_status_details(self, text):676 def _set_status_details(self, text):
597 self._status_details = get_dbus_string(text)677 self._status_details = get_dbus_string(text)
598 self.PropertyChanged("StatusDetails", self._status_details)678 self.PropertyChanged("StatusDetails", self._status_details)
@@ -600,9 +680,11 @@
600 status_details = property(_get_status_details, _set_status_details,680 status_details = property(_get_status_details, _set_status_details,
601 doc="The status message from apt.")681 doc="The status message from apt.")
602682
683 @locked
603 def _get_progress(self):684 def _get_progress(self):
604 return self._progress685 return self._progress
605686
687 @locked
606 def _set_progress(self, percent):688 def _set_progress(self, percent):
607 self._progress = dbus.Int32(percent)689 self._progress = dbus.Int32(percent)
608 self.PropertyChanged("Progress", self._progress)690 self.PropertyChanged("Progress", self._progress)
@@ -610,9 +692,11 @@
610 progress = property(_get_progress, _set_progress,692 progress = property(_get_progress, _set_progress,
611 doc="The progress of the transaction in percent.")693 doc="The progress of the transaction in percent.")
612694
695 @locked
613 def _get_progress_download(self):696 def _get_progress_download(self):
614 return self._progress_download697 return self._progress_download
615698
699 @locked
616 def _set_progress_download(self, progress_download):700 def _set_progress_download(self, progress_download):
617 self._progress_download = self._convert_struct(progress_download,701 self._progress_download = self._convert_struct(progress_download,
618 "sssxxs")702 "sssxxs")
@@ -626,9 +710,11 @@
626 "partially downloaded size and a status "710 "partially downloaded size and a status "
627 "message.")711 "message.")
628712
713 @locked
629 def _get_cancellable(self):714 def _get_cancellable(self):
630 return self._cancellable715 return self._cancellable
631716
717 @locked
632 def _set_cancellable(self, cancellable):718 def _set_cancellable(self, cancellable):
633 self._cancellable = dbus.Boolean(cancellable)719 self._cancellable = dbus.Boolean(cancellable)
634 self.PropertyChanged("Cancellable", self._cancellable)720 self.PropertyChanged("Cancellable", self._cancellable)
@@ -637,9 +723,11 @@
637 doc="If it's currently allowed to cancel the "723 doc="If it's currently allowed to cancel the "
638 "transaction.")724 "transaction.")
639725
726 @locked
640 def _get_term_attached(self):727 def _get_term_attached(self):
641 return self._term_attached728 return self._term_attached
642729
730 @locked
643 def _set_term_attached(self, attached):731 def _set_term_attached(self, attached):
644 self._term_attached = dbus.Boolean(attached)732 self._term_attached = dbus.Boolean(attached)
645 self.PropertyChanged("TerminalAttached", self._term_attached)733 self.PropertyChanged("TerminalAttached", self._term_attached)
@@ -649,9 +737,11 @@
649 "attached to the dpkg call of the "737 "attached to the dpkg call of the "
650 "transaction.")738 "transaction.")
651739
740 @locked
652 def _get_required_medium(self):741 def _get_required_medium(self):
653 return self._required_medium742 return self._required_medium
654743
744 @locked
655 def _set_required_medium(self, medium):745 def _set_required_medium(self, medium):
656 self._required_medium = self._convert_struct(medium, "ss")746 self._required_medium = self._convert_struct(medium, "ss")
657 self.PropertyChanged("RequiredMedium", self._required_medium)747 self.PropertyChanged("RequiredMedium", self._required_medium)
@@ -662,9 +752,11 @@
662 "of a required CD/DVD to install packages "752 "of a required CD/DVD to install packages "
663 "from.")753 "from.")
664754
755 @locked
665 def _get_config_file_conflict(self):756 def _get_config_file_conflict(self):
666 return self._config_file_conflict757 return self._config_file_conflict
667758
759 @locked
668 def _set_config_file_conflict(self, prompt):760 def _set_config_file_conflict(self, prompt):
669 if prompt is None:761 if prompt is None:
670 self._config_file_conflict = dbus.Struct(("", ""), signature="ss")762 self._config_file_conflict = dbus.Struct(("", ""), signature="ss")
@@ -739,6 +831,18 @@
739831
740 # Methods832 # Methods
741833
834 def _get_after(self):
835 return self._after
836
837 def _set_after(self, tid):
838 self.after = tid
839
840 after = property(_get_after, _set_after)
841
842 @locked
843 def _get_locale(self):
844 return self._locale
845
742 def _set_locale(self, locale_str):846 def _set_locale(self, locale_str):
743 """Set the language and encoding.847 """Set the language and encoding.
744848
@@ -755,11 +859,14 @@
755 except ValueError:859 except ValueError:
756 raise860 raise
757 else:861 else:
758 self.locale = dbus.String("%s.%s" % (lang, encoding))862 with self.lock:
759 self._translation = gettext.translation("aptdaemon",863 self._locale = dbus.String("%s.%s" % (lang, encoding))
760 fallback=True,864 self._translation = gettext.translation("aptdaemon",
761 languages=[lang])865 fallback=True,
762 self.PropertyChanged("locale", self.locale)866 languages=[lang])
867 self.PropertyChanged("locale", self._locale)
868
869 locale = property(_get_locale, _set_locale)
763870
764 @inline_callbacks871 @inline_callbacks
765 def _set_http_proxy(self, url, sender):872 def _set_http_proxy(self, url, sender):
@@ -776,6 +883,7 @@
776 self.http_proxy = dbus.String(url)883 self.http_proxy = dbus.String(url)
777 self.PropertyChanged("HttpProxy", self.http_proxy)884 self.PropertyChanged("HttpProxy", self.http_proxy)
778885
886 @locked
779 def _set_remove_obsoleted_depends(self, remove_obsoleted_depends):887 def _set_remove_obsoleted_depends(self, remove_obsoleted_depends):
780 """Set the handling of the removal of automatically installed888 """Set the handling of the removal of automatically installed
781 dependencies which are now obsoleted.889 dependencies which are now obsoleted.
@@ -788,6 +896,14 @@
788 self.PropertyChanged("RemoveObsoletedDepends",896 self.PropertyChanged("RemoveObsoletedDepends",
789 self.remove_obsoleted_depends)897 self.remove_obsoleted_depends)
790898
899 @locked
900 def _get_remove_obsoleted_depends(self):
901 return self._remove_obsoleted_depends
902
903 remove_obsoleted_depends = property(_get_remove_obsoleted_depends,
904 _set_remove_obsoleted_depends)
905
906 @locked
791 def _set_allow_unauthenticated(self, allow_unauthenticated):907 def _set_allow_unauthenticated(self, allow_unauthenticated):
792 """Set the handling of unauthenticated packages 908 """Set the handling of unauthenticated packages
793909
@@ -795,8 +911,16 @@
795 allow_unauthenticated -- True to allow packages that come from a 911 allow_unauthenticated -- True to allow packages that come from a
796 repository without a valid authentication signature912 repository without a valid authentication signature
797 """913 """
798 self.allow_unauthenticated = dbus.Boolean(allow_unauthenticated)914 self._allow_unauthenticated = dbus.Boolean(allow_unauthenticated)
799 self.PropertyChanged("AllowUnauthenticated", self.allow_unauthenticated)915 self.PropertyChanged("AllowUnauthenticated",
916 self._allow_unauthenticated)
917
918 @locked
919 def _get_allow_unauthenticated(self):
920 return self._allow_unauthenticated
921
922 allow_unauthenticated = property(_get_allow_unauthenticated,
923 _set_allow_unauthenticated)
800924
801 # pylint: disable-msg=C0103,C0322925 # pylint: disable-msg=C0103,C0322
802 @dbus.service.method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,926 @dbus.service.method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,
@@ -837,12 +961,12 @@
837 def _run(self, sender):961 def _run(self, sender):
838 yield self._check_foreign_user(sender)962 yield self._check_foreign_user(sender)
839 yield self._check_auth()963 yield self._check_auth()
840 self.queue.put(self.tid)964 yield self.queue.put(self.tid)
841 self.status = enums.STATUS_WAITING965 self.status = enums.STATUS_WAITING
842 next_trans = self.after966 next_trans = self.after
843 while next_trans:967 while next_trans:
844 yield next_trans._check_auth()968 yield next_trans._check_auth()
845 self.queue.put(next_trans.tid)969 yield self.queue.put(next_trans.tid)
846 next_trans.status = enums.STATUS_WAITING970 next_trans.status = enums.STATUS_WAITING
847 next_trans = next_trans.after971 next_trans = next_trans.after
848972
@@ -921,6 +1045,7 @@
921 log_trans.debug("Setting cancel event")1045 log_trans.debug("Setting cancel event")
922 self.cancelled = True1046 self.cancelled = True
923 self.status = enums.STATUS_CANCELLING1047 self.status = enums.STATUS_CANCELLING
1048 self.feedback.set()
924 self.paused = False1049 self.paused = False
925 return1050 return
926 raise errors.AptDaemonError("Could not cancel transaction")1051 raise errors.AptDaemonError("Could not cancel transaction")
@@ -944,7 +1069,7 @@
944 if self.status != enums.STATUS_SETTING_UP:1069 if self.status != enums.STATUS_SETTING_UP:
945 raise errors.TransactionAlreadyRunning()1070 raise errors.TransactionAlreadyRunning()
946 yield self._check_foreign_user(sender)1071 yield self._check_foreign_user(sender)
947 self.queue.worker.simulate(self)1072 yield self.queue.worker.simulate(self)
948 if self._idle_watch is not None:1073 if self._idle_watch is not None:
949 gobject.source_remove(self._idle_watch)1074 gobject.source_remove(self._idle_watch)
950 self._idle_watch = None1075 self._idle_watch = None
@@ -974,13 +1099,20 @@
974 try:1099 try:
975 slave_fd = os.open(ttyname, os.O_RDWR | os.O_NOCTTY)1100 slave_fd = os.open(ttyname, os.O_RDWR | os.O_NOCTTY)
976 if os.isatty(slave_fd):1101 if os.isatty(slave_fd):
977 self.terminal = dbus.String(ttyname)1102 with self.lock:
978 self.PropertyChanged("Terminal", self.terminal)1103 self._terminal = dbus.String(ttyname)
1104 self.PropertyChanged("Terminal", self._terminal)
979 else:1105 else:
980 raise errors.AptDaemonError("%s isn't a tty" % ttyname)1106 raise errors.AptDaemonError("%s isn't a tty" % ttyname)
981 finally:1107 finally:
982 os.close(slave_fd)1108 os.close(slave_fd)
9831109
1110 @locked
1111 def _get_terminal(self):
1112 return self._terminal
1113
1114 terminal = property(_get_terminal, _set_terminal)
1115
984 def _set_debconf(self, debconf_socket):1116 def _set_debconf(self, debconf_socket):
985 """Set the socket of the debconf proxy.1117 """Set the socket of the debconf proxy.
9861118
@@ -1002,8 +1134,15 @@
1002 raise errors.AptDaemonError("socket '%s' has to be owned by the "1134 raise errors.AptDaemonError("socket '%s' has to be owned by the "
1003 "owner of the "1135 "owner of the "
1004 "transaction" % debconf_socket)1136 "transaction" % debconf_socket)
1005 self.debconf = dbus.String(debconf_socket)1137 with self.lock:
1006 self.PropertyChanged("DebconfSocket", self.debconf)1138 self._debconf = dbus.String(debconf_socket)
1139 self.PropertyChanged("DebconfSocket", self._debconf)
1140
1141 @locked
1142 def _get_debconf(self):
1143 return self._debconf
1144
1145 debconf = property(_get_debconf, _set_debconf)
10071146
1008 # pylint: disable-msg=C0103,C03221147 # pylint: disable-msg=C0103,C0322
1009 @dbus_deferred_method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,1148 @dbus_deferred_method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,
@@ -1030,6 +1169,7 @@
1030 if not self.required_medium[0] == medium:1169 if not self.required_medium[0] == medium:
1031 raise errors.AptDaemonError("The medium '%s' isn't "1170 raise errors.AptDaemonError("The medium '%s' isn't "
1032 "requested." % medium)1171 "requested." % medium)
1172 self.feedback.set()
1033 self.paused = False1173 self.paused = False
10341174
1035 # pylint: disable-msg=C0103,C03221175 # pylint: disable-msg=C0103,C0322
@@ -1066,6 +1206,7 @@
1066 if not self.config_file_conflict[0] == config:1206 if not self.config_file_conflict[0] == config:
1067 raise errors.AptDaemonError("Invalid config file: %s" % config)1207 raise errors.AptDaemonError("Invalid config file: %s" % config)
1068 self.config_file_conflict_resolution = answer1208 self.config_file_conflict_resolution = answer
1209 self.feedback.set()
1069 self.paused = False1210 self.paused = False
10701211
1071 @inline_callbacks1212 @inline_callbacks
@@ -1132,10 +1273,6 @@
1132 if self.uid != uid:1273 if self.uid != uid:
1133 raise errors.ForeignTransaction()1274 raise errors.ForeignTransaction()
11341275
1135 def _set_kwargs(self, kwargs):
1136 """Set the kwargs which will be send to the AptWorker."""
1137 self.kwargs = kwargs
1138
1139 def gettext(self, msg):1276 def gettext(self, msg):
1140 """Translate the given message to the language of the transaction.1277 """Translate the given message to the language of the transaction.
1141 Fallback to the system default.1278 Fallback to the system default.
@@ -1184,6 +1321,7 @@
1184 log.debug("emitting queue changed")1321 log.debug("emitting queue changed")
1185 self.emit("queue-changed")1322 self.emit("queue-changed")
11861323
1324 @inline_callbacks
1187 def put(self, tid):1325 def put(self, tid):
1188 """Add an item to the queue."""1326 """Add an item to the queue."""
1189 trans = self.limbo.pop(tid)1327 trans = self.limbo.pop(tid)
@@ -1197,7 +1335,7 @@
1197 # the transaction has been started1335 # the transaction has been started
1198 if not self.worker.trans:1336 if not self.worker.trans:
1199 trans.progress = 91337 trans.progress = 9
1200 self.worker.simulate(trans)1338 yield self.worker.simulate(trans)
12011339
1202 if trans._idle_watch is not None:1340 if trans._idle_watch is not None:
1203 gobject.source_remove(trans._idle_watch)1341 gobject.source_remove(trans._idle_watch)
@@ -1271,6 +1409,7 @@
1271 signal.signal(signal.SIGQUIT, self._sigquit)1409 signal.signal(signal.SIGQUIT, self._sigquit)
1272 signal.signal(signal.SIGTERM, self._sigquit)1410 signal.signal(signal.SIGTERM, self._sigquit)
1273 self.options = options1411 self.options = options
1412 self.mainloop = gobject.MainLoop()
1274 if connect == True:1413 if connect == True:
1275 if bus is None:1414 if bus is None:
1276 bus = dbus.SystemBus()1415 bus = dbus.SystemBus()
@@ -1362,7 +1501,7 @@
1362 self._check_for_inactivity)1501 self._check_for_inactivity)
1363 log.debug("Waiting for calls")1502 log.debug("Waiting for calls")
1364 try:1503 try:
1365 mainloop.run()1504 self.mainloop.run()
1366 except KeyboardInterrupt:1505 except KeyboardInterrupt:
1367 self.Quit(None)1506 self.Quit(None)
13681507
@@ -1853,7 +1992,7 @@
1853 """Request a shutdown of the daemon."""1992 """Request a shutdown of the daemon."""
1854 log.info("Quitting was requested")1993 log.info("Quitting was requested")
1855 log.debug("Quitting main loop...")1994 log.debug("Quitting main loop...")
1856 mainloop.quit()1995 self.mainloop.quit()
1857 log.debug("Exit")1996 log.debug("Exit")
18581997
1859 @inline_callbacks1998 @inline_callbacks
18601999
=== removed file 'aptdaemon/loop.py'
--- aptdaemon/loop.py 2010-05-03 05:49:33 +0000
+++ aptdaemon/loop.py 1970-01-01 00:00:00 +0000
@@ -1,32 +0,0 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""Main loop for aptdaemon."""
4# Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20__author__ = "Sebastian Heinlein <devel@glatzor.de>"
21
22__all__ = ("mainloop", "get_main_loop")
23
24import gobject
25
26mainloop = gobject.MainLoop()
27
28def get_main_loop():
29 """Return the gobject main loop as a singelton."""
30 return mainloop
31
32# vim:ts=4:sw=4:et
330
=== modified file 'aptdaemon/progress.py'
--- aptdaemon/progress.py 2011-09-30 15:22:13 +0000
+++ aptdaemon/progress.py 2011-10-02 05:32:23 +0000
@@ -33,7 +33,6 @@
3333
34import enums34import enums
35import lock35import lock
36from loop import mainloop
3736
38# Required to get translatable strings extraced by xgettext37# Required to get translatable strings extraced by xgettext
39_ = lambda s: s38_ = lambda s: s
@@ -210,9 +209,6 @@
210 len(items)) % {"files":209 len(items)) % {"files":
211 " ".join(items)}210 " ".join(items)}
212 self.transaction.status_details = msg211 self.transaction.status_details = msg
213
214 while gobject.main_context_default().pending():
215 gobject.main_context_default().iteration()
216 return True212 return True
217213
218 def start(self):214 def start(self):
@@ -232,8 +228,7 @@
232 self.transaction.required_medium = medium, drive228 self.transaction.required_medium = medium, drive
233 self.transaction.paused = True229 self.transaction.paused = True
234 self.transaction.status = enums.STATUS_WAITING_MEDIUM230 self.transaction.status = enums.STATUS_WAITING_MEDIUM
235 while self.transaction.paused:231 self.transaction.feedback.wait()
236 gobject.main_context_default().iteration()
237 self.transaction.status = enums.STATUS_DOWNLOADING232 self.transaction.status = enums.STATUS_DOWNLOADING
238 if self.transaction.cancelled:233 if self.transaction.cancelled:
239 return False234 return False
@@ -309,6 +304,7 @@
309 self.child_pid = pid304 self.child_pid = pid
310 os.close(self.status_child_fd)305 os.close(self.status_child_fd)
311 log.debug("Child pid: %s", pid)306 log.debug("Child pid: %s", pid)
307 loop = gobject.MainLoop()
312 watchers = []308 watchers = []
313 flags = gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP309 flags = gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP
314 if self.transaction.terminal:310 if self.transaction.terminal:
@@ -318,13 +314,12 @@
318 watchers.append(gobject.io_add_watch(self.master_fd, flags,314 watchers.append(gobject.io_add_watch(self.master_fd, flags,
319 self._copy_io_master, terminal_fd))315 self._copy_io_master, terminal_fd))
320 # Monitor the child process316 # Monitor the child process
321 watchers.append(gobject.child_watch_add(pid, self._on_child_exit))317 watchers.append(gobject.child_watch_add(pid, self._on_child_exit, loop))
322 # Watch for status updates318 # Watch for status updates
323 watchers.append(gobject.io_add_watch(self.status_parent_fd,319 watchers.append(gobject.io_add_watch(self.status_parent_fd,
324 gobject.IO_IN,320 gobject.IO_IN,
325 self._on_status_update))321 self._on_status_update))
326 while self._child_exit == -1:322 loop.run()
327 gobject.main_context_default().iteration()
328 for id in watchers:323 for id in watchers:
329 gobject.source_remove(id)324 gobject.source_remove(id)
330 # Restore the settings of the transaction terminal325 # Restore the settings of the transaction terminal
@@ -341,9 +336,10 @@
341 pass336 pass
342 return os.WEXITSTATUS(self._child_exit)337 return os.WEXITSTATUS(self._child_exit)
343338
344 def _on_child_exit(self, pid, condition):339 def _on_child_exit(self, pid, condition, loop):
345 log.debug("Child exited: %s", condition)340 log.debug("Child exited: %s", condition)
346 self._child_exit = condition341 self._child_exit = condition
342 loop.quit()
347 return False343 return False
348344
349 def _on_status_update(self, source, condition):345 def _on_status_update(self, source, condition):
@@ -397,8 +393,6 @@
397 signal.signal(signal.SIGINT, interrupt_handler)393 signal.signal(signal.SIGINT, interrupt_handler)
398 # Make sure that exceptions of the child are not catched by apport394 # Make sure that exceptions of the child are not catched by apport
399 sys.excepthook = sys.__excepthook__395 sys.excepthook = sys.__excepthook__
400
401 mainloop.quit()
402 # Switch to the language of the user396 # Switch to the language of the user
403 if self.transaction.locale:397 if self.transaction.locale:
404 os.putenv("LANG", self.transaction.locale)398 os.putenv("LANG", self.transaction.locale)
@@ -496,8 +490,8 @@
496 self.transaction.config_file_conflict = (current, new)490 self.transaction.config_file_conflict = (current, new)
497 self.transaction.paused = True491 self.transaction.paused = True
498 self.transaction.status = enums.STATUS_WAITING_CONFIG_FILE_PROMPT492 self.transaction.status = enums.STATUS_WAITING_CONFIG_FILE_PROMPT
499 while self.transaction.paused:493 #FIXME: Should we only wait some time?
500 gobject.main_context_default().iteration()494 self.transaction.feedback.wait()
501 log.debug("Sending config file answer: %s",495 log.debug("Sending config file answer: %s",
502 self.transaction.config_file_conflict_resolution)496 self.transaction.config_file_conflict_resolution)
503 if self.transaction.config_file_conflict_resolution == "replace":497 if self.transaction.config_file_conflict_resolution == "replace":
504498
=== modified file 'aptdaemon/utils.py'
--- aptdaemon/utils.py 2010-05-03 05:49:33 +0000
+++ aptdaemon/utils.py 2011-10-02 05:32:23 +0000
@@ -25,8 +25,11 @@
25__all__ = ("deprecated",)25__all__ = ("deprecated",)
2626
27import functools27import functools
28import threading
28import warnings29import warnings
2930
31from defer import Deferred
32
30def deprecated(func):33def deprecated(func):
31 """This is a decorator which can be used to mark functions34 """This is a decorator which can be used to mark functions
32 as deprecated. It will result in a warning being emitted35 as deprecated. It will result in a warning being emitted
@@ -48,4 +51,31 @@
48 return func(*args, **kwargs)51 return func(*args, **kwargs)
49 return new_func52 return new_func
5053
54def locked(func):
55 """Protect the called method by a lock."""
56 def _locked(*args, **kwargs):
57 self = args[0]
58 with self.lock:
59 return func(*args, **kwargs)
60 return _locked
61
62def defer_to_thread(func):
63 """Wrap the decorated message to a Deferred which will be called
64 in a separated thread.
65 """
66 def _deferred_to_thread(*args, **kwargs):
67 def __deferred_to_thread(deferred):
68 try:
69 result = func(*args, **kwargs)
70 except Exception, error:
71 deferred.errback(error)
72 else:
73 deferred.callback(result)
74 deferred = Deferred()
75 #FIXME: Would be nice to have a thread pool and a Queue here
76 thread = threading.Thread(target=__deferred_to_thread, args=[deferred])
77 thread.run()
78 return deferred
79 return _deferred_to_thread
80
51# vim:ts=4:sw=4:et81# vim:ts=4:sw=4:et
5282
=== modified file 'aptdaemon/worker.py'
--- aptdaemon/worker.py 2011-09-18 06:31:09 +0000
+++ aptdaemon/worker.py 2011-10-02 05:32:23 +0000
@@ -1,4 +1,4 @@
1#!/usr/bin/env python1#/usr/bin/env python
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
3"""Provides AptWorker which processes transactions."""3"""Provides AptWorker which processes transactions."""
4# Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>4# Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>
@@ -29,6 +29,7 @@
29import sys29import sys
30import tempfile30import tempfile
31import time31import time
32import threading
32import traceback33import traceback
3334
34import apt35import apt
@@ -54,6 +55,7 @@
54 DaemonDpkgInstallProgress, \55 DaemonDpkgInstallProgress, \
55 DaemonDpkgReconfigureProgress, \56 DaemonDpkgReconfigureProgress, \
56 DaemonDpkgRecoverProgress57 DaemonDpkgRecoverProgress
58from utils import locked, defer_to_thread
5759
58log = logging.getLogger("AptDaemon.Worker")60log = logging.getLogger("AptDaemon.Worker")
5961
@@ -105,6 +107,8 @@
105 self._status_frozen = None107 self._status_frozen = None
106 self.plugins = {}108 self.plugins = {}
107 self._load_plugins()109 self._load_plugins()
110 self.lock = threading.Lock()
111 self.worker_thread = None
108112
109 def _load_plugins(self):113 def _load_plugins(self):
110 """Load the plugins from setuptools' entry points."""114 """Load the plugins from setuptools' entry points."""
@@ -153,10 +157,14 @@
153 transaction -- core.Transcation instance to run157 transaction -- core.Transcation instance to run
154 """158 """
155 log.info("Processing transaction %s", transaction.tid)159 log.info("Processing transaction %s", transaction.tid)
156 if self.trans:160 with self.lock:
157 raise Exception("There is already a running transaction")161 if self.trans:
158 self.trans = transaction162 raise Exception("There is already a running transaction")
159 gobject.idle_add(self._process_transaction, transaction)163 self.trans = transaction
164 self.worker_thread = threading.Thread(target=self._process_transaction,
165 args=[transaction],
166 name="WorkerThread")
167 self.worker_thread.start()
160168
161 def _emit_transaction_done(self, trans):169 def _emit_transaction_done(self, trans):
162 """Emit the transaction-done signal.170 """Emit the transaction-done signal.
@@ -174,6 +182,7 @@
174 trans.progress = 11182 trans.progress = 11
175 # FIXME: Check if the transaction has been just simulated. So we could183 # FIXME: Check if the transaction has been just simulated. So we could
176 # skip marking the changes a second time.184 # skip marking the changes a second time.
185 self.lock.acquire()
177 try:186 try:
178 lock.wait_for_lock(trans)187 lock.wait_for_lock(trans)
179 # Prepare the package cache188 # Prepare the package cache
@@ -236,14 +245,15 @@
236 else:245 else:
237 trans.exit = EXIT_SUCCESS246 trans.exit = EXIT_SUCCESS
238 finally:247 finally:
248 self.lock.release()
239 trans.progress = 100249 trans.progress = 100
240 self.last_action_timestamp = time.time()250 self.last_action_timestamp = time.time()
241 tid = trans.tid[:]251 tid = trans.tid[:]
242 self.trans = None252 with self.lock:
253 self.trans = None
243 self._emit_transaction_done(trans)254 self._emit_transaction_done(trans)
244 lock.release()255 lock.release()
245 log.info("Finished transaction %s", tid)256 log.info("Finished transaction %s", tid)
246 return False
247257
248 def commit_packages(self, trans, install, reinstall, remove, purge, upgrade,258 def commit_packages(self, trans, install, reinstall, remove, purge, upgrade,
249 downgrade, simulate=False):259 downgrade, simulate=False):
@@ -360,7 +370,6 @@
360 "available."), pkg_ver, pkg_name)370 "available."), pkg_ver, pkg_name)
361 elif pkg_rel:371 elif pkg_rel:
362 self._set_candidate_release(pkg, pkg_rel)372 self._set_candidate_release(pkg, pkg_rel)
363
364373
365 def enable_distro_comp(self, trans, component):374 def enable_distro_comp(self, trans, component):
366 """Enable given component in the sources list.375 """Enable given component in the sources list.
@@ -381,7 +390,8 @@
381 finally:390 finally:
382 os.umask(old_umask)391 os.umask(old_umask)
383392
384 def add_repository(self, trans, src_type, uri, dist, comps, comment, sourcesfile):393 def add_repository(self, trans, src_type, uri, dist, comps, comment,
394 sourcesfile):
385 """Add given repository to the sources list.395 """Add given repository to the sources list.
386396
387 Keyword arguments:397 Keyword arguments:
@@ -444,7 +454,6 @@
444 log.info("Adding vendor key from keyserver: %s %s", keyid, keyserver)454 log.info("Adding vendor key from keyserver: %s %s", keyid, keyserver)
445 trans.status = STATUS_DOWNLOADING455 trans.status = STATUS_DOWNLOADING
446 trans.progress = 101456 trans.progress = 101
447 last_pulse = time.time()
448 #FIXME: Use gobject.spawn_async and deferreds in the worker457 #FIXME: Use gobject.spawn_async and deferreds in the worker
449 # Alternatively we could use python-pyme directly for a better458 # Alternatively we could use python-pyme directly for a better
450 # error handling. Or the --status-fd of gpg459 # error handling. Or the --status-fd of gpg
@@ -453,12 +462,8 @@
453 "--recv", keyid], stderr=subprocess.STDOUT,462 "--recv", keyid], stderr=subprocess.STDOUT,
454 stdout=subprocess.PIPE, close_fds=True)463 stdout=subprocess.PIPE, close_fds=True)
455 while proc.poll() is None:464 while proc.poll() is None:
456 while gobject.main_context_default().pending():465 time.sleep(0.5)
457 gobject.main_context_default().iteration()466 trans.progress = 101
458 time.sleep(0.05)
459 if time.time() - last_pulse > 0.3:
460 trans.progress = 101
461 last_pulse = time.time()
462 if proc.returncode != 0:467 if proc.returncode != 0:
463 stdout = unicode(proc.stdout.read(), 468 stdout = unicode(proc.stdout.read(),
464 # that can return "None", in this case, just469 # that can return "None", in this case, just
@@ -941,12 +946,15 @@
941 frozen_dir = tempfile.mkdtemp(prefix="aptdaemon-frozen-status")946 frozen_dir = tempfile.mkdtemp(prefix="aptdaemon-frozen-status")
942 shutil.copy(self._status_orig, frozen_dir)947 shutil.copy(self._status_orig, frozen_dir)
943 self._status_frozen = os.path.join(frozen_dir, "status")948 self._status_frozen = os.path.join(frozen_dir, "status")
949 self.lock.release()
944 try:950 try:
945 yield951 yield
946 finally:952 finally:
953 self.lock.acquire()
947 shutil.rmtree(frozen_dir)954 shutil.rmtree(frozen_dir)
948 self._status_frozen = None955 self._status_frozen = None
949956
957 @defer_to_thread
950 def simulate(self, trans):958 def simulate(self, trans):
951 """Return the dependencies which will be installed by the transaction,959 """Return the dependencies which will be installed by the transaction,
952 the content of the dpkg status file after the transaction would have960 the content of the dpkg status file after the transaction would have
@@ -958,8 +966,9 @@
958 log.info("Simulating trans: %s" % trans.tid)966 log.info("Simulating trans: %s" % trans.tid)
959 trans.status = STATUS_RESOLVING_DEP967 trans.status = STATUS_RESOLVING_DEP
960 try:968 try:
961 trans.depends, trans.download, trans.space, \969 with self.lock:
962 trans.unauthenticated = self._simulate_helper(trans)970 trans.depends, trans.download, trans.space, \
971 trans.unauthenticated = self._simulate_helper(trans)
963 except TransactionFailed, excep:972 except TransactionFailed, excep:
964 trans.error = excep973 trans.error = excep
965 except Exception, excep:974 except Exception, excep:
@@ -980,7 +989,6 @@
980 trans.exit = EXIT_FAILED989 trans.exit = EXIT_FAILED
981 trans.progress = 100990 trans.progress = 100
982 self.last_action_timestamp = time.time()991 self.last_action_timestamp = time.time()
983 raise trans.error
984992
985 def _simulate_helper(self, trans):993 def _simulate_helper(self, trans):
986 depends = [[], [], [], [], [], [], []]994 depends = [[], [], [], [], [], [], []]
@@ -1341,11 +1349,11 @@
1341 trans.status = STATUS_FINISHED1349 trans.status = STATUS_FINISHED
1342 self.last_action_timestamp = time.time()1350 self.last_action_timestamp = time.time()
1343 tid = self.trans.tid[:]1351 tid = self.trans.tid[:]
1344 trans = self.trans1352 with self.lock:
1345 self.trans = None1353 trans = self.trans
1354 self.trans = None
1346 self._emit_transaction_done(trans)1355 self._emit_transaction_done(trans)
1347 log.info("Finished transaction %s", tid)1356 log.info("Finished transaction %s", tid)
1348 return False
13491357
1350 def simulate(self, trans):1358 def simulate(self, trans):
1351 depends = [[], [], [], [], [], [], []]1359 depends = [[], [], [], [], [], [], []]

Subscribers

People subscribed via source and target branches

to status/vote changes: