[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