Add timeout to exif reads (#34)
parent
463e57d98b
commit
d66ca0a281
90
lib/Exif.php
90
lib/Exif.php
|
@ -21,6 +21,7 @@ class Exif {
|
||||||
1 => array('pipe', 'w'),
|
1 => array('pipe', 'w'),
|
||||||
2 => array('pipe', 'w'),
|
2 => array('pipe', 'w'),
|
||||||
], self::$staticPipes);
|
], self::$staticPipes);
|
||||||
|
stream_set_blocking(self::$staticPipes[1], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function closeStaticExiftoolProc() {
|
public static function closeStaticExiftoolProc() {
|
||||||
|
@ -30,10 +31,17 @@ class Exif {
|
||||||
fclose(self::$staticPipes[1]);
|
fclose(self::$staticPipes[1]);
|
||||||
fclose(self::$staticPipes[2]);
|
fclose(self::$staticPipes[2]);
|
||||||
proc_terminate(self::$staticProc);
|
proc_terminate(self::$staticProc);
|
||||||
|
self::$staticProc = null;
|
||||||
|
self::$staticPipes = null;
|
||||||
}
|
}
|
||||||
} catch (\Exception $ex) {}
|
} catch (\Exception $ex) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function restartStaticExiftoolProc() {
|
||||||
|
self::closeStaticExiftoolProc();
|
||||||
|
self::ensureStaticExiftoolProc();
|
||||||
|
}
|
||||||
|
|
||||||
public static function ensureStaticExiftoolProc() {
|
public static function ensureStaticExiftoolProc() {
|
||||||
if (self::$noStaticProc) {
|
if (self::$noStaticProc) {
|
||||||
return;
|
return;
|
||||||
|
@ -126,23 +134,49 @@ class Exif {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getExifFromLocalPathWithStaticProc(string &$path) {
|
/**
|
||||||
fwrite(self::$staticPipes[0], "$path\n-json\n-api\nQuickTimeUTC=1\n-execute\n");
|
* Read from non blocking handle or throw timeout
|
||||||
fflush(self::$staticPipes[0]);
|
* @param resource $handle
|
||||||
|
* @param int $timeout milliseconds
|
||||||
|
* @param string $delimiter null for eof
|
||||||
|
*/
|
||||||
|
private static function readOrTimeout($handle, $timeout, $delimiter=null) {
|
||||||
|
$buf = '';
|
||||||
|
$waitedMs = 0;
|
||||||
|
|
||||||
$buf = "";
|
while ($waitedMs < $timeout && ($delimiter ? !str_ends_with($buf, $delimiter) : !feof($handle))) {
|
||||||
$readyToken = "\n{ready}\n";
|
$r = stream_get_contents($handle);
|
||||||
while (!str_ends_with($buf, $readyToken)) {
|
if (empty($r)) {
|
||||||
$r = fread(self::$staticPipes[1], 1);
|
$waitedMs++;
|
||||||
if ($r === false) {
|
usleep(1000);
|
||||||
error_log("PANIC: Something went wrong with static exiftool process");
|
continue;
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
$buf .= $r;
|
$buf .= $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
$buf = substr($buf, 0, strrpos($buf, $readyToken));
|
if ($waitedMs >= $timeout) {
|
||||||
|
throw new \Exception('Timeout');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getExifFromLocalPathWithStaticProc(string &$path) {
|
||||||
|
fwrite(self::$staticPipes[0], "$path\n-json\n-api\nQuickTimeUTC=1\n-execute\n");
|
||||||
|
fflush(self::$staticPipes[0]);
|
||||||
|
|
||||||
|
$readyToken = "\n{ready}\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$buf = self::readOrTimeout(self::$staticPipes[1], 2000, $readyToken);
|
||||||
|
$tokPos = strrpos($buf, $readyToken);
|
||||||
|
$buf = substr($buf, 0, $tokPos);
|
||||||
return self::processStdout($buf);
|
return self::processStdout($buf);
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
error_log("ERROR: Exiftool may have crashed, restarting process [$path]");
|
||||||
|
self::restartStaticExiftoolProc();
|
||||||
|
throw new \Exception("Nothing to read from Exiftool");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getExifFromLocalPathWithSeparateProc(string &$path) {
|
private static function getExifFromLocalPathWithSeparateProc(string &$path) {
|
||||||
|
@ -151,14 +185,19 @@ class Exif {
|
||||||
1 => array('pipe', 'w'),
|
1 => array('pipe', 'w'),
|
||||||
2 => array('pipe', 'w'),
|
2 => array('pipe', 'w'),
|
||||||
], $pipes);
|
], $pipes);
|
||||||
$stdout = stream_get_contents($pipes[1]);
|
stream_set_blocking($pipes[1], false);
|
||||||
|
|
||||||
// Clean up
|
try {
|
||||||
|
$stdout = self::readOrTimeout($pipes[1], 5000);
|
||||||
|
return self::processStdout($stdout);
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
error_log("Exiftool timeout: [$path]");
|
||||||
|
throw new \Exception("Could not read from Exiftool");
|
||||||
|
} finally {
|
||||||
fclose($pipes[1]);
|
fclose($pipes[1]);
|
||||||
fclose($pipes[2]);
|
fclose($pipes[2]);
|
||||||
proc_close($proc);
|
proc_terminate($proc);
|
||||||
|
}
|
||||||
return self::processStdout($stdout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -176,18 +215,23 @@ class Exif {
|
||||||
|
|
||||||
// Write the file to exiftool's stdin
|
// Write the file to exiftool's stdin
|
||||||
// Warning: this is slow for big files
|
// Warning: this is slow for big files
|
||||||
stream_copy_to_stream($handle, $pipes[0]);
|
// Copy a maximum of 20MB; this may be $$$
|
||||||
|
stream_copy_to_stream($handle, $pipes[0], 20 * 1024 * 1024);
|
||||||
fclose($pipes[0]);
|
fclose($pipes[0]);
|
||||||
|
|
||||||
// Get output from exiftool
|
// Get output from exiftool
|
||||||
$stdout = stream_get_contents($pipes[1]);
|
stream_set_blocking($pipes[1], false);
|
||||||
|
try {
|
||||||
// Clean up
|
$stdout = self::readOrTimeout($pipes[1], 5000);
|
||||||
|
return self::processStdout($stdout);
|
||||||
|
} catch (\Exception $ex) {
|
||||||
|
error_log("Exiftool timeout for file stream: " . $ex->getMessage());
|
||||||
|
throw new \Exception("Could not read from Exiftool");
|
||||||
|
} finally {
|
||||||
fclose($pipes[1]);
|
fclose($pipes[1]);
|
||||||
fclose($pipes[2]);
|
fclose($pipes[2]);
|
||||||
proc_close($proc);
|
proc_terminate($proc);
|
||||||
|
}
|
||||||
return self::processStdout($stdout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get json array from stdout of exiftool */
|
/** Get json array from stdout of exiftool */
|
||||||
|
|
Loading…
Reference in New Issue