extism / php-sdk

Extism PHP Host SDK - easily run WebAssembly modules / plugins from PHP applications
https://extism.org
BSD 3-Clause "New" or "Revised" License
20 stars 4 forks source link

feat: add Host Functions support #13

Closed mhmd-azeez closed 9 months ago

mhmd-azeez commented 10 months ago

Fixes #11

nilslice commented 10 months ago

cc @AtlantisPleb

mhmd-azeez commented 10 months ago

I will need to do some tests regarding memory leaks, since FFI callbacks seem to leak by default: https://www.php.net/manual/en/ffi.examples-callback.php

Although this works, this functionality is not supported on all libffi platforms, is not efficient and leaks resources by the end of request. It is therefore recommended to minimize the usage of PHP callbacks.

mhmd-azeez commented 9 months ago

After a lot of experimenting, it seems like host functions leak memory, which is expected because it's a documented behavior of FFI + callbacks. So, I have decided to mark it as experimental for now, until we can find ways around this or maybe we get more user feedback.

However, the good news is, http servers like Apache are already designed to handle such scenarios fairly well, for example, let's consider this test:

for ($i = 0; $i < 10; $i++) {
    $kvstore = [];

    $kvRead = new HostFunction("kv_read", [ExtismValType::I64], [ExtismValType::I64], function (CurrentPlugin $p, string $key) use (&$kvstore) {
        return $kvstore[$key] ?? "\0\0\0\0";
    });

    $kvWrite = new HostFunction("kv_write", [ExtismValType::I64, ExtismValType::I64], [], function (string $key, string $value) use (&$kvstore) {
        $kvstore[$key] = $value;
    });

    $plugin = new Plugin($manifest, true, [$kvRead, $kvWrite]);
    $output = $plugin->call("count_vowels", "Hello World!");

    if ($i % 1 === 0) {
        echo "Iteration: $i => " . $lib->count . PHP_EOL;
    }
}

This is how Apache behaves under test:

https://github.com/extism/php-sdk/assets/16880059/7af966b1-7eb3-42d4-885e-c0b7a348713d

As for freeing memory,

Managed data lives together with the returned FFI\CData object, and is released when the last reference to that object is released by regular PHP reference counting or GC. Unmanaged data should be released by calling FFI::free(), when no longer needed.

-- PHP Docs

We are only using managed data, so we should be fine.