package UCW::Subprocess;

# Copyright (c) 2013  Petr Baudis <pasky@ucw.cz>

# This is something like AnyEvent::Subprocess, but without 14769
# dependencies and not overdesigned.

# XXX: This works properly only in case of AnyEvent backend with
# race-free AE::child (that can handle already-zombie processes).
# Specifically, EV is _broken_ in this regard!

use warnings;
use strict;
use v5.10;

use AnyEvent;
use Carp;

# Arugments:
# command => [argv]
# before => CODEREF (optional)
# cb => CODEREF
# persistent => BOOL (1 means not killed when goes out of scope)
sub new {
	my $class = shift;
	my (%args) = @_;

	ref $args{command} eq 'ARRAY' or croak 'command not defined or not an array ref';
	$args{persistent} //= 0;

	my $self = bless { %args }, $class;

	$self->spawn();
	$self;
}

sub spawn {
	my $self = shift;
	my $pid = fork;
	if ($pid < 0) {
		die "fork: $!";
	}

	if ($pid == 0) {
		# Child process
		$self->{before}($self) if $self->{before};
		exec(@{$self->{command}}) or die "system: $?, $!";
	}

	# We keep the "pid" value as an independent referenced scalar,
	# so that we do not need to reference $self within AE::child
	# callback. That would cause DESTROY to never be called when
	# the caller drops the reference to us as another would still
	# linger in the child callback.
	my $metapid = \$pid;
	$self->{pid} = $metapid;
	my $cb = $self->{cb};

	my $w; $w = AE::child $pid, sub {
		my ($pid, $status) = @_;
		$$metapid = 0; # Don't kill from DESTROY anymore
		$cb->($pid, $status) if $cb;
		# The AE::child watcher is held up to now:
		undef $w;
	};
}

sub DESTROY {
	my $self = shift;
	if (not $self->{persistent}
	    and $self->{pid} and ${$self->{pid}}) {
		kill 'TERM', ${$self->{pid}};
	}
}

1;
