Skip to content

feat: Implement true streaming and signal handling #191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: PHP already converts null
fix: Buffer needs to be copied over
fix: sizeof(GClosure) works now
feat: Added more tests and an example
  • Loading branch information
L3tum committed Feb 27, 2023
commit 1a5f7f003faf07a31e1677708f5a5d7c9f02f370
12 changes: 12 additions & 0 deletions examples/streaming.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env php
<?php

require dirname(__DIR__) . '/vendor/autoload.php';

use Jcupitt\Vips;
use Jcupitt\Vips\VipsSource;

$source = VipsSource::newFromFile(dirname(__DIR__) . '/tests/images/img_0076.jpg');
$target = Vips\VipsTarget::newToFile(dirname(__DIR__) . "/tests/images/target.jpg");
$image = Vips\Image::newFromSource($source);
$image->writeToTarget($target, '.jpg[Q=95]');
53 changes: 48 additions & 5 deletions src/FFI.php
Original file line number Diff line number Diff line change
Expand Up @@ -434,11 +434,54 @@ private static function init(): void
const char* g_param_spec_get_blurb (GParamSpec* psp);

typedef struct _GClosure GClosure;
typedef void (*marshaler)(struct GClosure* closure, GValue* return_value, int n_param_values, const GValue* param_values, void* invocation_hint, void* marshal_data);
struct _GClosure {
int in_marshal : 1;
int is_invalid : 1;
marshaler marshal;
typedef void (*marshaler)(
struct GClosure* closure,
GValue* return_value,
int n_param_values,
const GValue* param_values,
void* invocation_hint,
void* marshal_data
);

typedef struct _GClosureNotifyData GClosureNotifyData;
struct _GClosureNotifyData
{
void* data;
GClosureNotify notify;
};
struct _GClosure
{
/*< private >*/
int ref_count : 15; /* (atomic) */
/* meta_marshal is not used anymore but must be zero for historical reasons
as it was exposed in the G_CLOSURE_N_NOTIFIERS macro */
int meta_marshal_nouse : 1; /* (atomic) */
int n_guards : 1; /* (atomic) */
int n_fnotifiers : 2; /* finalization notifiers (atomic) */
int n_inotifiers : 8; /* invalidation notifiers (atomic) */
int in_inotify : 1; /* (atomic) */
int floating : 1; /* (atomic) */
/*< protected >*/
int derivative_flag : 1; /* (atomic) */
/*< public >*/
int in_marshal : 1; /* (atomic) */
int is_invalid : 1; /* (atomic) */

/*< private >*/ marshaler marshal;
/*< protected >*/ void* data;

/*< private >*/ GClosureNotifyData *notifiers;

/* invariants/constraints:
* - ->marshal and ->data are _invalid_ as soon as ->is_invalid==TRUE
* - invocation of all inotifiers occurs prior to fnotifiers
* - order of inotifiers is random
* inotifiers may _not_ free/invalidate parameter values (e.g. ->data)
* - order of fnotifiers is random
* - each notifier may only be removed before or during its invocation
* - reference counting may only happen prior to fnotify invocation
* (in that sense, fnotifiers are really finalization handlers)
*/
};
long g_signal_connect_closure(GObject* object, const char* detailed_signal, GClosure *closure, bool after);
GClosure* g_closure_ref(GClosure* closure);
Expand Down
7 changes: 4 additions & 3 deletions src/GObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ public function signalConnect(string $name, Closure $callback): void
$returnBufferLength = $callback($buffer);
\FFI::memcpy($bufferPointer, $buffer, $returnBufferLength);
FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength);
FFI::gobject()->g_value_set_pointer(\FFI::addr($params[1]), $bufferPointer);
};
$marshalers['seek'] = static function (
CData $gClosure,
Expand Down Expand Up @@ -171,7 +170,9 @@ public function signalConnect(string $name, Closure $callback): void
$bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1]));
$bufferLength = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[2]));
$buffer = \FFI::string($bufferPointer, $bufferLength);
FFI::gobject()->g_value_set_int64($returnValue, $callback($buffer));
$returnBufferLength = $callback($buffer);
\FFI::memcpy($bufferPointer, $buffer, $returnBufferLength);
FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength);
};
$marshalers['finish'] = static function (
CData $gClosure,
Expand Down Expand Up @@ -211,7 +212,7 @@ public function signalConnect(string $name, Closure $callback): void
}

$go = \FFI::cast(FFI::ctypes('GObject'), $this->pointer);
$gc = FFI::gobject()->g_closure_new_simple(64, null);
$gc = FFI::gobject()->g_closure_new_simple(\FFI::sizeof(FFI::ctypes('GClosure')), null);
$gc->marshal = $marshalers[$name];
FFI::gobject()->g_signal_connect_closure($go, $name, $gc, 0);
}
Expand Down
6 changes: 3 additions & 3 deletions src/VipsSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static function newFromDescriptor(int $descriptor): self
{
$pointer = FFI::vips()->vips_source_new_from_descriptor($descriptor);

if (\FFI::isNull($pointer)) {
if ($pointer === null) {
throw new Exception("can't create source from descriptor $descriptor");
}

Expand All @@ -48,7 +48,7 @@ public static function newFromFile(string $filename): self
{
$pointer = FFI::vips()->vips_source_new_from_file($filename);

if (\FFI::isNull($pointer)) {
if ($pointer === null) {
throw new Exception("can't create source from filename $filename");
}

Expand All @@ -68,7 +68,7 @@ public static function newFromMemory(string $data): self
\FFI::memcpy($memory, $data, $n);
$pointer = FFI::vips()->vips_source_new_from_memory($memory, $n);

if (\FFI::isNull($pointer)) {
if ($pointer === null) {
throw new Exception("can't create source from memory");
}

Expand Down
15 changes: 12 additions & 3 deletions src/VipsTarget.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,41 @@ public function __construct(\FFI\CData $pointer)
parent::__construct($pointer);
}

/**
* @throws Exception
*/
public static function newToDescriptor(int $descriptor): self
{
$pointer = FFI::vips()->vips_target_new_to_descriptor($descriptor);
if (\FFI::isNull($pointer)) {
if ($pointer === null) {
throw new Exception("can't create output target from descriptor $descriptor");
}

return new self($pointer);
}

/**
* @throws Exception
*/
public static function newToFile(string $filename): self
{
$pointer = FFI::vips()->vips_target_new_to_file($filename);

if (\FFI::isNull($pointer)) {
if ($pointer === null) {
throw new Exception("can't create output target from filename $filename");
}

return new self($pointer);
}

/**
* @throws Exception
*/
public static function newToMemory(): self
{
$pointer = FFI::vips()->vips_target_new_to_memory();

if (\FFI::isNull($pointer)) {
if ($pointer === null) {
throw new Exception("can't create output target from memory");
}

Expand Down
19 changes: 19 additions & 0 deletions tests/StreamingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ public function testFromFileToFile(): void
unlink($target->filename());
}

public function testNoLeak(): void
{
$lastUsage = 0;
for ($i = 0; $i < 10; $i++) {
$filename = tempnam(sys_get_temp_dir(), 'image');
$source = new VipsSourceResource(fopen(__DIR__ . '/images/img_0076.jpg', 'rb'));
$target = new VipsTargetResource(fopen($filename, 'wb+'));
$image = Image::newFromSource($source);
$image->writeToTarget($target, '.jpg[Q=95]');
unlink($filename);
$usage = memory_get_peak_usage(true);
$diff = $usage - $lastUsage;
if ($lastUsage !== 0 && $diff > 0) {
echo "LEAK LEAK LEAK" . PHP_EOL;
}
$lastUsage = $usage;
}
}

public function testFromFileToDescriptor(): void
{
// NOTE(L3tum): There is no way to get a file descriptor in PHP :)
Expand Down