Пакет · thread

Логирование и отладка

Winter Thread предлагает гибкую систему обработки вывода фоновых процессов, управляемую двумя параметрами start(): $debugMode и $outputTarget.

Почему по умолчанию /dev/null

Когда родитель открывает pipe ($outputTarget = null), но никогда его не читает, буфер ОС (~64 КБ) заполняется, дочерний процесс блокируется на write() и в итоге получает Broken pipe — это молча убивает фоновые задачи. Значение по умолчанию '/dev/null' полностью это предотвращает: вывод отбрасывается ОС без буферизации. Передавайте null только тогда, когда активно читаете через readOutput() / readError().

Стратегия 1 — Fire and forget (по умолчанию)

Самый безопасный режим для продакшн-задач. Вывод отбрасывается; pipe не открывается, поэтому родитель может запустить задачу и сразу освободить объект Thread.

  • start() — эквивалент start([], false, '/dev/null')
  • $debugMode: false — ошибки PHP в дочернем процессе подавляются.
  • $outputTarget: '/dev/null' — вывод отбрасывается ОС.
php
<?php
require 'vendor/autoload.php';

use Flytachi\Winter\Thread\Runnable;
use Flytachi\Winter\Thread\Thread;

$thread = new Thread(new class implements Runnable {
  public function run(array $args): void {
      // Тяжёлая задача — вывод по умолчанию отбрасывается
      file_put_contents('/tmp/result.json', json_encode(['done' => true]));
  }
});

$pid = $thread->start(); // безопасный fire-and-forget
echo "Started background process PID: $pid\n";
// Объект Thread можно освободить здесь; риска Broken pipe нет

Стратегия 2 — Лог в файл

Рекомендуется для staging и продакшна, когда нужна запись. Весь вывод (echo, var_dump, ошибки PHP) дописывается в файл.

  • start(true, '/path/to/file.log')
  • $debugMode: true — ошибки PHP включены и видны в логе.
  • $outputTarget: '/path/file.log' — вывод дописывается.
php
<?php
require 'vendor/autoload.php';

use Flytachi\Winter\Thread\Runnable;
use Flytachi\Winter\Thread\Thread;

$thread = new Thread(new class implements Runnable {
  public function run(array $args): void {
      echo "Task started at: " . date('Y-m-d H:i:s') . PHP_EOL;
      echo "Processing..." . PHP_EOL;
      // Предупреждение — видно в логе, потому что debugMode = true
      $result = 10 / 0;
  }
});

$logFile = __DIR__ . '/worker.log';
$pid = $thread->start(debugMode: true, outputTarget: $logFile);
echo "PID: $pid — logging to {$logFile}\n";

Читаем лог:

bash
tail -f worker.log

Ожидаемое содержимое:

text
Task started at: 2025-12-19 10:30:00
Processing...
Warning: Division by zero in ... on line XX

Стратегия 3 — Интерактивный режим (pipe)

Для локальной разработки и отладки. Родитель читает вывод дочернего процесса в реальном времени. Вы обязаны явно передать null и обязаны активно опрашивать readOutput() / readError(), пока процесс жив — иначе буфер pipe заполнится и вызовет Broken pipe.

  • start(true, null)
  • $debugMode: true — ошибки PHP включены.
  • $outputTarget: null — вывод передаётся родителю через pipe.
php
<?php
require 'vendor/autoload.php';

use Flytachi\Winter\Thread\Runnable;
use Flytachi\Winter\Thread\Thread;

$thread = new Thread(new class implements Runnable {
  public function run(array $args): void {
      for ($i = 1; $i <= 3; $i++) {
          echo "Step {$i}..." . PHP_EOL;
          sleep(1);
      }
      trigger_error("Custom warning for demo", E_USER_WARNING);
  }
});

$pid = $thread->start(debugMode: true, outputTarget: null);
echo "Interactive session started, PID: $pid\n\n";

// ВАЖНО: активно вычитывайте pipe, пока процесс работает
while ($thread->isAlive()) {
  $out = $thread->readOutput();
  if ($out !== '') {
      echo '[STDOUT] ' . rtrim($out) . "\n";
  }
  $err = $thread->readError();
  if ($err !== '') {
      echo '[STDERR] ' . rtrim($err) . "\n";
  }
  usleep(250_000);
}

// Дочитываем остаток вывода после завершения процесса
$out = $thread->readOutput();
if ($out !== '') {
  echo '[STDOUT] ' . rtrim($out) . "\n";
}
$err = $thread->readError();
if ($err !== '') {
  echo '[STDERR] ' . rtrim($err) . "\n";
}

$exitCode = $thread->join();
echo "\nProcess $pid finished with exit code: $exitCode\n";

Ожидаемый вывод (появляется постепенно):

text
Interactive session started, PID: 12345

[STDOUT] Step 1...
[STDOUT] Step 2...
[STDOUT] Step 3...
[STDERR] Warning: Custom warning for demo in ... on line XX

Process 12345 finished with exit code: 0

Итог

Режим $outputTarget $debugMode Когда использовать
Fire and forget '/dev/null' (по умолчанию) false Продакшн-задачи в фоне
Лог в файл '/path/file.log' true Staging / продакшн с журналом
Интерактивный pipe null (явно) true Локальная разработка, чтение вывода в реальном времени