Skip to content

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

Description

@lyrixx

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

Activity

  1. bwoebi commented on May 22, 2025

    @bwoebi
    Member

    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.

  2. lyrixx commented on May 22, 2025

    @lyrixx
    Author

    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?

  3. lyrixx commented on May 22, 2025

    @lyrixx
    Author

    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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions