Xarrion
Xarrion

Reputation: 47

pthreads access to resource globally in PHP

I'm having problems with accessing resources from global context using php pthreads for Windows.

The logic is very simple: there's a Server as a main class, only one, which will:

Problem is, the resource of logfile handle is somehow totally messed up from inside the thread. When I create the resource, it's fine, I can write in it. When I try to call the log from inside the running thread, the resource of logfile handler appears to be integer 0, not even a resource.

Here's my code:

$main_server = new CMainServer();
$main_server->init();
$main_server->go();

$main_server->log("All done");

Inside the CMainServer class:

class CMainServer
{
  private $logfile = null;

  public function init()
  {
    $this->logfile = fopen('wstext.log', 'w');
  }

  public function log($str)
  {
    if ($this->logfile === null)
    {
      echo "[".date("H:i:s", time())."]: logfile is null<BR />";
      return false;
    }

    if (!is_resource($this->logfile))
    {
      echo "[".date("H:i:s", time())."]: logfile is NOT a resource, can't write {$str}<BR />";
      return false;
    }

    echo "[".date("H:i:s", time())."]: logfile is resource, not null, writing {$str}<BR />";
    flush();

    fwrite($this->logfile, "[".date("H:i:s", time())."]: {$str}\r\n");
    return true;
  }

  public function go()
  {
    $this->log('Before creating a thread');

    $first_thread = new CThread();
    $first_thread->start(PTHREADS_INHERIT_ALL | PTHREADS_ALLOW_GLOBALS);
    $second_thread = new CThread();
    $second_thread->start(PTHREADS_INHERIT_ALL | PTHREADS_ALLOW_GLOBALS);
    $first_thread->join();
    $second_thread->join();
  }

  public function __destruct()
  {
    if ($this->logfile)
      fclose($this->logfile);
  }
}

And, finally, the CThread class:

class CThread extends Thread
{
  public function run()
  {
    global $main_server;
    $thread_id = $this->getThreadId();

    Thread::globally(function()
    {
      for ($i = 0; $i < 2; $i++)
      {
        $main_server->log("({$i}) writing random number ".rand(0, 100)." to log from running thread id={$thread_id}");
        sleep(1);
      }
    });
  }
}

Result is sad:

[13:38:10]: logfile is NOT a resource, can't write (0) writing random number 21 to log from running thread id=9080
[13:38:11]: logfile is NOT a resource, can't write (1) writing random number 91 to log from running thread id=9080
[13:38:10]: logfile is NOT a resource, can't write (0) writing random number 16 to log from running thread id=17316
[13:38:11]: logfile is NOT a resource, can't write (1) writing random number 50 to log from running thread id=17316
[13:38:10]: logfile is resource, not null, writing Before creating a thread
[13:38:12]: logfile is resource, not null, writing All done

So while I'm outside the thread, all is fine. However, from within a thread the $logfile is not a resource at all.

I tried different options: tried calling from CThread::run() a global function:

function LogFromThread($i, $thread_id)
{
  global $main_server;

  $main_server->log("({$i}) writing random number ".rand(0, 100)." to log from running thread id={$thread_id}");
}

The result is the same.

Tried without Thread::globally() at all, but all for no good.

I'm running Apache/2.4.10 (Win32) OpenSSL/1.0.1i PHP/5.6.3, tried pthreads version 2.0.8, 2.0.9. Also tried with PHP 7RC2 and RC3, but there seems to be a problem to start a new thread at all, apache logs an error, so I returned to 5.6.3.

Maybe someone could give me a hint out about this?

Much appreciated! =)

Upvotes: 1

Views: 1060

Answers (1)

Joe Watkins
Joe Watkins

Reputation: 17158

Do not try to use globals in threads.

The PTHREADS_ALLOW_GLOBALS constant and functionality is there for a special use case, it is not intended for use by everyone, in addition globally has been removed in v3.

There is a much tidier way of doing what you want to do, that actually works.

Resources are officially unsupported, that doesn't mean you can't use them, it means you shouldn't expect to be able to share them among contexts.

In this case, you don't need to share the resource and so should not try, nor should you try to do anything in the global scope.

Follows is some PHP7 code (which I recommend new projects should use as pthreads v3 is vastly superior to v2):

<?php
class Logger extends Threaded {

    public function __construct(string $file) {
        $this->file = $file;
    }

    private function getHandle() {
        if (!self::$handle) {
            self::$handle = fopen($this->file, "a");
        }

        return self::$handle;
    }

    public function log(string $message, ... $args) {
        return $this->synchronized(function() use($message, $args) {
            return vfprintf($this->getHandle(), $message, $args);
        });
    }

    private $file;  
    private static $handle;
}

class My extends Thread {

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }

    public function run() {
        while (@$i++<100) {
            $this->logger->log("Hello %s from %s #%lu\n", 
                "World", __CLASS__, $this->getThreadId());

            /* just simulating work, don't need to wait here */
            $this->synchronized(function(){
                $this->wait(1000);
            });
        }
    }

    private $logger;
}

$logger = new Logger("/tmp/log.txt");
$threads = [];
while (@$i++ < 10) {
    $threads[$i] = new My($logger);
    $threads[$i]->start();
}

foreach ($threads as $thread)
    $thread->join();
?>

In the Logger you will notice that the handle is stored statically, which for pthreads means thread-local.

This means that each thread has a handle to the log, which is how resources are intended to be used in PHP.

You will also notice, the log method is wrapped in a synchronized block, the reason is this: If many threads attempt to write a log at the same time, you will have a log filled with gibberish.

Synchronizing provides mutual exclusion, so that only one thread may write the log at a time, I create ten threads and have them all write to the log one hundred times, no gibberish comes out, it will behave like that everywhere.

A little bit about file locking (flock): On some operating systems, in some circumstances, append is atomic. It's worth mentioning because you should never rely on this, nor should you try to force the writes to be atomic with flock, since flock only places advisory locks on a file: A process executing with the correct permissions is free to ignore flocks and manipulate the file anyway.

Scary stuff, don't rely on append being atomic, and don't rely on flock is the only sensible advice in the context of multi-threading.

On a side note, if you think you have found a bug in pthreads (v3, PHP7), please report it on github.

Upvotes: 4

Related Questions