File-Unpack2

 view release on metacpan or  search on metacpan

lib/File/Unpack2.pm  view on Meta::CPAN

        # PR_SET_PDEATHSIG(SIGKILL) makes the kernel kill this helper the instant its parent worker
        # dies, so a stuck helper (e.g. a malicious cyclic-hardlink tar spinning on stat()/linkat())
        # is not orphaned to ppid==1 to spin forever when the worker is force-stopped mid-unpack
        # (Minion "stop job", a hard restart, a crash) - the case the stall watchdog can't cover
        # because it dies with the worker. PR_SET_PDEATHSIG is 1 on every Linux arch. Where the
        # syscall number was unresolved this is skipped; then that narrow orphan window remains and
        # such orphans must be cleaned up out of band (or the worker stopped via systemd
        # KillMode=control-group, which reaps the whole cgroup).
        eval { syscall($SYS_PRCTL, 1, POSIX::SIGKILL()) if $SYS_PRCTL; 1 };
        # ... and put each helper in its own process group so _kill_family can reap the whole family
        # (grandchildren included) while we are alive.
        setpgrp(0, 0);    # builtin; POSIX::setpgrp is unimplemented on modern perl
      };
    }

  my $has_i_redir = 0;
  my $has_o_redir = 0;
  my $has_e_redir = 0;

  ## The ugly truth is, there might be multiple commands with pipes.
  ## We need to provide all of them with the proper redirects.

t/10-stall-timeout.t  view on Meta::CPAN

  # progress) every 0.3s for ~3s, then writes a marker file at the very end. With stall_timeout=2,
  # if pipe output did not count as progress it would be killed at ~2s and the marker never written.
  my ($u, $src, $dest) = stall_setup(
    [$^X, '-e', 'for (1..10) { print STDERR "x"; select undef,undef,undef,0.3 } open my $f, ">", "pipe.done" or die; print $f "ok"; close $f']);
  plan skip_all => 'could not build fixture' unless $u;

  my $done = guarded(30, sub { $u->unpack($src) });
  ok($done, 'unpack() returned');
  my $marker;
  find(sub { $marker = $File::Find::name if $_ eq 'pipe.done' }, $dest);
  ok($marker, 'a pipe-only helper ran to completion - pipe output kept it alive, not falsely killed');
};

subtest 'a reparented grandchild holding the pipe is reaped via its process group' => sub {
  # The direct child backgrounds a grandchild (which inherits the stdout pipe and the helper's
  # process group) and then exits, so the grandchild reparents to init - invisible to the ppid walk.
  # Only killing the captured process group reaps it; otherwise finish() blocks on the still-open
  # pipe forever (caught by the alarm as a failure).
  my ($u, $src) = stall_setup(['/bin/sh', '-c', 'sleep 999 & exit 0']);
  plan skip_all => 'could not build fixture' unless $u;

t/10-stall-timeout.t  view on Meta::CPAN

      chomp($hpid = <$f>);
      close $f;
      last if $hpid;
    }
    select undef, undef, undef, 0.1;
  }
  unless ($hpid && kill 0, $hpid) {
    kill 'KILL', $worker; waitpid $worker, 0;
    plan skip_all => 'helper did not start (fixture/timing)';
  }
  ok(1, 'helper started and is alive while the worker runs');

  # "stop job" kills the WORKER (not its process group). The helper is in its own group, so this
  # signal does not reach it - PR_SET_PDEATHSIG is what must take it down.
  kill 'KILL', $worker;
  waitpid $worker, 0;

  my $alive = 1;
  for (1 .. 100) { $alive = kill 0, $hpid; last unless $alive; select undef, undef, undef, 0.1 }
  ok(!$alive, 'helper was SIGKILLed when its parent worker died - not orphaned to spin forever');
  kill 'KILL', $hpid if $alive;    # cleanup only if the safeguard regressed
};

done_testing;



( run in 1.472 second using v1.01-cache-2.11-cpan-14f38c9f855 )