[pve-devel] [PATCH common] systemd: add wait_for_unit_removed
Wolfgang Bumiller
w.bumiller at proxmox.com
Tue Jun 18 15:24:46 CEST 2019
On Tue, Jun 18, 2019 at 03:23:06PM +0200, Wolfgang Bumiller wrote:
> And split out the common code into a systemd_call() sub.
>
> This connects to the systemd bus and waits for a UnitRemoved
> event to occur for the specified unit, with an optional
> timeout.
>
> We'll use this in qemu-server to wait for a VM's scope to
> disappear completely before trying to start a VM's scope
> anew.
>
> Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
> ---
> src/PVE/Systemd.pm | 135 ++++++++++++++++++++++++++++++++++++++---------------
> 1 file changed, 98 insertions(+), 37 deletions(-)
>
> diff --git a/src/PVE/Systemd.pm b/src/PVE/Systemd.pm
> index e44b719..d4c6840 100644
> --- a/src/PVE/Systemd.pm
> +++ b/src/PVE/Systemd.pm
> @@ -7,8 +7,63 @@ use Net::DBus qw(dbus_uint32 dbus_uint64);
> use Net::DBus::Callback;
> use Net::DBus::Reactor;
>
> +# $code should take the parameters ($interface, $reactor, $finish_callback).
> +#
> +# $finish_callback can be used by dbus-signal-handlers to stop the reactor.
> +#
> +# In order to even start waiting on the reactor, $code needs to return undef, if it returns a
> +# defined value instead, it is assumed that this is the result already and we can stop.
> # NOTE: This calls the dbus main loop and must not be used when another dbus
> -# main loop is being used as we need to wait for the JobRemoved signal.
> +# main loop is being used as we need to wait signals.
> +sub systemd_call($;$) {
> + my ($code, $timeout) = @_;
> +
> + my $bus = Net::DBus->system();
> + my $reactor = Net::DBus::Reactor->main();
> +
> + my $service = $bus->get_service('org.freedesktop.systemd1');
> + my $if = $service->get_object('/org/freedesktop/systemd1', 'org.freedesktop.systemd1.Manager');
> +
> + my ($finished, $current_result, $timer);
> + my $finish_callback = sub {
> + my ($result) = @_;
> +
> + $current_result = $result;
> +
> + $finished = 1;
> +
> + if (defined($timer)) {
> + $reactor->remove_timeout($timer);
> + $timer = undef;
> + }
> +
> + if (defined($reactor)) {
> + $reactor->shutdown();
> + $reactor = undef;
> + }
> + };
> +
> + my $result = $code->($if, $reactor, $finish_callback);
> + # Are we done immediately?
> + return $result if defined $result;
> +
> + # Alterantively $finish_callback may have been called already?
> + return $current_result if $finished;
> +
> + # Otherwise wait:
> + my $on_timeout = sub {
> + $finish_callback->(undef);
> + die "timeout waiting on systemd\n";
> + };
> + $timer = $reactor->add_timeout($timeout * 1000, Net::DBus::Callback->new(method => $on_timeout))
> + if defined($timeout);
> +
> + $reactor->run();
> + $reactor->shutdown() if defined($reactor); # $finish_callback clears it
> +
> + return $current_result;
> +}
> +
> # Polling the job status instead doesn't work because this doesn't give us the
> # distinction between success and failure.
> #
> @@ -35,47 +90,53 @@ sub enter_systemd_scope {
> }
> }
>
> - my $job;
> - my $done = 0;
> + systemd_call(sub {
> + my ($if, $reactor, $finish_cb) = @_;
>
> - my $bus = Net::DBus->system();
> - my $reactor = Net::DBus::Reactor->main();
> + my $job;
>
> - my $service = $bus->get_service('org.freedesktop.systemd1');
> - my $if = $service->get_object('/org/freedesktop/systemd1', 'org.freedesktop.systemd1.Manager');
> - # Connect to the JobRemoved signal since we want to wait for it to finish
> - my $sigid;
> - my $timer;
> - my $cleanup = sub {
> - my ($no_shutdown) = @_;
> - $if->disconnect_from_signal('JobRemoved', $sigid) if defined($if);
> - $if = undef;
> - $sigid = undef;
> - $reactor->remove_timeout($timer) if defined($timer);
> - $timer = undef;
> - return if $no_shutdown;
> - $reactor->shutdown();
> - };
> + $if->connect_to_signal('JobRemoved', sub {
> + my ($id, $removed_job, $signaled_unit, $result) = @_;
> + return if $signaled_unit ne $unit || $removed_job ne $job;
> + if ($result ne 'done') {
> + # I seem to remember $reactor->run() catching die() at some point?
> + # so better call finish to be sure...:
> + $finish_cb->(0);
> + die "systemd job failed\n";
> + } else {
> + print "HERE 2\n";
Leftover debug output...
> + $finish_cb->(1);
> + }
> + });
>
> - $sigid = $if->connect_to_signal('JobRemoved', sub {
> - my ($id, $removed_job, $signaled_unit, $result) = @_;
> - return if $signaled_unit ne $unit || $removed_job ne $job;
> - $cleanup->(0);
> - die "systemd job failed\n" if $result ne 'done';
> - $done = 1;
> - });
> + $job = $if->StartTransientUnit($unit, 'fail', $properties, []);
>
> - my $on_timeout = sub {
> - $cleanup->(0);
> - die "systemd job timed out\n";
> - };
> + return undef;
> + }, $timeout);
> +}
>
> - $timer = $reactor->add_timeout($timeout * 1000, Net::DBus::Callback->new(method => $on_timeout))
> - if defined($timeout);
> - $job = $if->StartTransientUnit($unit, 'fail', $properties, []);
> - $reactor->run();
> - $cleanup->(1);
> - die "systemd job never completed\n" if !$done;
> +sub wait_for_unit_removed($;$) {
> + my ($unit, $timeout) = @_;
> +
> + systemd_call(sub {
> + my ($if, $reactor, $finish_cb) = @_;
> +
> + my $unit_obj = eval { $if->GetUnit($unit) };
> + return 1 if !$unit_obj;
> +
> + $if->connect_to_signal('UnitRemoved', sub {
> + my ($id, $removed_unit) = @_;
> + $finish_cb->(1) if $removed_unit eq $unit_obj;
> + });
> +
> + # Deal with what we lost between GetUnit() and connecting to UnitRemoved:
> + my $unit_obj_new = eval { $if->GetUnit($unit) };
> + if (!$unit_obj_new) {
> + return 1;
> + }
> +
> + return undef;
> + }, $timeout);
> }
>
> 1;
> --
> 2.11.0
More information about the pve-devel
mailing list