Skip to content

FFI: A state is kept somewhere between two HTTP requst #18621

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

Closed
lyrixx opened this issue May 22, 2025 · 3 comments
Closed

FFI: A state is kept somewhere between two HTTP requst #18621

lyrixx opened this issue May 22, 2025 · 3 comments

Comments

@lyrixx
Copy link

lyrixx commented May 22, 2025

Description

The following code:

<?php

class LibNotifyDriver
{
    private static FFI $ffi;

    public function send(): bool
    {
        $this->initialize();
        $notification = self::$ffi->notify_notification_new(
            'a title',
            'a body',
            null,
        );
        $value = self::$ffi->notify_notification_show($notification, null);
        self::$ffi->g_object_unref($notification);

        return $value;
    }

    private static function initialize(): void
    {
        if (isset(self::$ffi)) {
            return;
        }

        $header = <<<'C'
            typedef bool gboolean;
            typedef void* gpointer;
            typedef struct _NotifyNotification NotifyNotification;
            typedef struct _GTypeInstanceError GError;

            gboolean notify_init(const char *app_name);
            gboolean notify_is_initted (void);
            void notify_uninit (void);
            NotifyNotification *notify_notification_new(const char *summary, const char *body, const char *icon);
            gboolean notify_notification_show (NotifyNotification *notification, GError **error);
            void g_object_unref (gpointer object);
            C;

        $ffi = FFI::cdef($header, 'libnotify.so.4');

        if (!$ffi) {
            throw new RuntimeException('Unable to load libnotify');
        }

        self::$ffi = $ffi;

        if (!self::$ffi->notify_init('app')) {
            throw new RuntimeException('Unable to initialize libnotify');
        }

        if (!self::$ffi->notify_is_initted()) {
            throw new RuntimeException('Libnotify has not been initialized');
        }
    }
}

$driver = new LibNotifyDriver();
$driver->send();

Resulted in this output:

>…ire/dev/github.com/jolicode/JoliNotif(ffi) php -S 127.0.0.1:9999 test.php 
[Thu May 22 15:14:12 2025] PHP 8.4.7 Development Server (http://127.0.0.1:9999) started
[Thu May 22 15:14:14 2025] 127.0.0.1:42474 Accepted
[Thu May 22 15:14:14 2025] 127.0.0.1:42474 Closing
[Thu May 22 15:14:15 2025] 127.0.0.1:42480 Accepted

(process:2370852): GLib-GObject-WARNING **: 15:14:15.844: cannot register existing type 'NotifyNotification'

(process:2370852): GLib-CRITICAL **: 15:14:15.844: g_once_init_leave: assertion 'result != 0' failed

(process:2370852): GLib-GObject-CRITICAL **: 15:14:15.844: g_object_new_valist: assertion 'G_TYPE_IS_OBJECT (object_type)' failed

(process:2370852): libnotify-CRITICAL **: 15:14:15.844: notify_notification_show: assertion 'notification != NULL' failed

(process:2370852): GLib-GObject-CRITICAL **: 15:14:15.844: g_object_unref: assertion 'G_IS_OBJECT (object)' failed
[Thu May 22 15:14:15 2025] 127.0.0.1:42480 Closing

But I expected this output instead:

>…ire/dev/github.com/jolicode/JoliNotif(ffi) php -S 127.0.0.1:9999 test.php 
[Thu May 22 15:14:12 2025] PHP 8.4.7 Development Server (http://127.0.0.1:9999) started
[Thu May 22 15:14:14 2025] 127.0.0.1:42474 Accepted
[Thu May 22 15:14:14 2025] 127.0.0.1:42474 Closing
[Thu May 22 15:14:15 2025] 127.0.0.1:42480 Accepted
[Thu May 22 15:14:15 2025] 127.0.0.1:42480 Closing

We found this issue via jolicode/JoliNotif#120

In cli, we had some troubles, so I refactored the code to allow only once instance. But with a web SAPI, it does not work. On the very first request, it works, but on the second one it does not.

I tested with the cli-server SAPI, and FPM SAPI

PHP Version

PHP 8.4.7 (cli) (built: May  9 2025 06:54:08) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.7, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.7, Copyright (c), by Zend Technologies
    with blackfire v1.92.32~linux-x64-non_zts84, https://blackfire.io, by Blackfire

Operating System

Ubuntu 22.04.5 LTS

@bwoebi
Copy link
Member

bwoebi commented May 22, 2025

Well, yes. That state is global, process bound. The PHP runtime has no idea of the lifecycle of any FFI-included libraries. The function of that library here seems to assume that it's only called once per process. That's your responsibility to maintain, not PHP's.

@bwoebi bwoebi closed this as not planned Won't fix, can't repro, duplicate, stale May 22, 2025
@lyrixx
Copy link
Author

lyrixx commented May 22, 2025

Thanks for the answer. But if FPM keeps a bound between every requests, How can I, at the PHP userland level, re-use this bound?

@lyrixx
Copy link
Author

lyrixx commented May 22, 2025

We managed to do that, and it work well. What would be awesome, is to be able to write such code:

try {
    $ffi = FFI::scope('libnotify');
} catch (\Throwable) {
    $ffi = FFI::load(__DIR__ . '/lib.h');
}

I said that, because we distribute a lib, and it would be more convenient

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants