php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #49010 spl_object_hash() not unique for Objects used in concurrent (forked) environment
Submitted: 2009-07-21 20:54 UTC Modified: 2010-12-20 12:46 UTC
From: benjamin at josefus dot net Assigned:
Status: Not a bug Package: SPL related
PHP Version: 5.3SVN-2009-07-21 (snap) OS: Ubuntu 9.04, Kernel2.6.27
Private report: No CVE-ID: None
 [2009-07-21 20:54 UTC] benjamin at josefus dot net
Description:
------------
I am working on a Task Management (java-like Threads) where each Task  implements a pcntl_fork(), which works with shared Objects using IPC and Semaphore to access concurrently and synchronized. 
The Synchronisation should happen by unique Key per Object and SHM Segment (delivered by spl_object_hash). I am using sem_acquire(), sem_release(), shm_attach(), shm_release(), shm_[put|get]_var() and so on. 

When providing two concurrent Tasks with the same Object to be shared, spl_object_hash delivers two different Hashes, when invoked in each Task.

Interestingly, if i call spl_object_hash for the shared Object before it is assigned to both "Producer" and "Worker" Task, the Hash IS unique, as it should be!

In short: 
- new Object 
- fork -> spl_object_hash(Object) is NOT unique

- new Object 
- call spl_object_hash(Object) first off 
- fork -> spl_object_hash(Object) IS unique and correct

System Notes:
PHP 5.3.0 (cli) (built: Jul 21 2009 22:19:17)
Configure Command 	'./configure' '--enable-pcntl' '--enable-shmop' '--enable-sysvmsg' '--enable-sysvsem' '--enable-sysvshm' '--with-apxs2=/usr/bin/apxs2' '--disable-short-tags' '--with-openssl' '--with-zlib' '--enable-bcmath' '--with-bz2=/bin/bzip2' '--enable-calendar' '--with-curl' '--with-curlwrappers' '--enable-exif' '--enable-ftp' '--with-gd' '--with-jpeg-dir=/usr/lib' '--with-png-dir=/usr/lib' '--with-xpm-dir=/usr/lib' '--with-t1lib' '--enable-gd-native-ttf' '--enable-gd-jis-conv' '--with-gettext' '--with-imap' '--with-imap-ssl' '--with-ldap' '--with-ldap-sasl' '--enable-mbstring' '--with-mcrypt' '--with-mhash' '--with-mysql=mysqlnd' '--with-mysqli=mysqlnd' '--with-pdo-mysql' '--with-pspell' '--with-readline' '--with-snmp' '--enable-soap' '--enable-sockets' '--without-sqlite' '--enable-sqlite-utf8' '--with-tidy' '--enable-wddx' '--with-xmlrpc' '--with-xsl' '--enable-zip' '--with-pear' '--with-kerberos' '--with-pgsql' '--with-pdo-pgsql' 

Reproduce code:
---------------
$shared = new MySharedObj();
spl_object_hash($shared); //toggle this line to for in/correct result  

/**
 * $shared is referenced member variable in each task
 */
$producer = new ProducerTask($shared); 
$worker = new WorkerTask($shared);      

$producer->start();
$worker->start(); //outputs different Hash for $shared provided by spl_object_hash


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2009-07-21 21:12 UTC] benjamin at josefus dot net
This is the result with spl_object_hash() invoked first off (as it should be):

not yet shared, initialize SHM 0000000016c4f93000000000506bf64a
Worker:   --> Wait!, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 0, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 0, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 1, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 1, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 2, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 2, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 3, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 3, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 4, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 4, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Wait!, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 5, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 5, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 6, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 6, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 7, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 7, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 8, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 8, ID: 0000000016c4f93000000000506bf64a
Producer: --> Producing 9, ID: 0000000016c4f93000000000506bf64a
Worker:   --> Working on 9, ID: 0000000016c4f93000000000506bf64a


This one happens when spl_object_hash() has not been invoked first 
off:

not yet shared, initialize SHM 0000000051f9a9a50000000056018292
not yet shared, initialize SHM 00000000187ccfd60000000067032181
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 0, ID: 0000000051f9a9a50000000056018292
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 1, ID: 0000000051f9a9a50000000056018292
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 2, ID: 0000000051f9a9a50000000056018292
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 3, ID: 0000000051f9a9a50000000056018292
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 4, ID: 0000000051f9a9a50000000056018292
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 5, ID: 0000000051f9a9a50000000056018292
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 6, ID: 0000000051f9a9a50000000056018292
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 7, ID: 0000000051f9a9a50000000056018292
Worker:   --> Wait!, ID: 00000000187ccfd60000000067032181
Producer: --> Producing 8, ID: 0000000051f9a9a50000000056018292
Producer: --> Producing 9, ID: 0000000051f9a9a50000000056018292)
 [2009-07-21 22:13 UTC] jani@php.net
Where does it say that this function returns unique hash for objects run 
from forked PHP instances? And if you claim it should, provide a short 
but complete(!) reproducing script..
 [2009-07-22 10:39 UTC] benjamin at josefus dot net
In fact, there ain't no single word defining that spl_object_hash() should work in forked environments. For sure! 
But if an instance of class "Foo" is created BEFORE forking, shouldn't the object's ID be the same, in both forked and parent processes? 

Therefore, I expected the unique ID to be created for an object on contruct-Time. But this is not happening. It will be assigned at first time calling spl_object_hash() for an object. This is quite unsafe in distributed and concurrent enviroments. 


A simple functional example below:
---------------------------------
class SharedObject
{
    public function __construct($callSplObjectHash = false)
    {
        if ($callSplObjectHash) 
            spl_object_hash($this);
    }
}

abstract class Task
{
    protected $_pid;

    protected $_isChild;

    public function __construct()
    {	
        $this->_isChild = false;	
    }
	
    /**
     * Starts this Task running
     */
    public function start() 
    {
    	// try to fork and acquire PID
    	try {
	    $pid = pcntl_fork();
            if ($pid === 0) {	 // in chid process
		self::sleep(10); // sleep and release CPU after Fork
		$this->_isChild = true;			
		$this->run();	 // starts the logic routine
		exit();
            } else if ($pid){ 	 // in parent process
                $this->_pid = $pid;
                $this->_isChild = false;
            } else {
                throw new Excpetion('Could not do pcntl_fork(). Negative Return Value');
            }
        } catch (Exception $e) {
            throw new Exception('Unable to Start Task. Cause: ' . $e->getMessage());
        }
    }

    public static function sleep($millisecs)
    {
        usleep($millisecs * 1000);
    }

    abstract protected function run();
} 

class Producer extends Task
{
    /**
     * @var SharedObject
     */
    protected $o;
    
    public function __construct(SharedObject $o)
    {
        $this->o = $o;
    }

    protected function run()
    {
        echo 'Producer::run()  ' . spl_object_hash($this->o) . PHP_EOL;
    }
}
	

class Worker extends Task
{
    /**
     * @var SharedObject
     */
    protected $o;
    
    public function __construct(SharedObject $o)
    {
        $this->o = $o;
    }
	
    protected function run()
    {
        echo 'Worker::run()    ' . spl_object_hash($this->o) . PHP_EOL;
    }
} 

// without spl_object_hash() called in Constructor
$s = new SharedObject();
$p = new Producer($s);
$w = new Worker($s);
$w->start();
$p->start();

// with spl_object_hash() called in Constructor
$s = new SharedObject(true);
$p = new Producer($s);
$w = new Worker($s);
$w->start();
$p->start();)
 [2009-07-22 12:12 UTC] iliaa@php.net
Thank you for taking the time to write to us, but this is not
a bug. Please double-check the documentation available at
http://www.php.net/manual/ and the instructions on how to report
a bug at http://bugs.php.net/how-to-report.php

Forks are not threads, memory is not shared, so you end up with 2 copies 
of the executor, hence repetitive object ids.
 [2009-07-22 12:29 UTC] benjamin at josefus dot net
I know that after a fork 2 processes work on their very own memory space. in fact, they work with two different objects. 

But then, why is the object hash the same for both Object Copies in concurrent Task::run() when spl_object_hash() is called (at least once) BEFORE fork?

I mean, this is a quite curious behavior, don't you think?)
 [2010-12-20 12:46 UTC] jani@php.net
-Package: Tidy +Package: *General Issues
 [2010-12-20 12:46 UTC] jani@php.net
-Package: *General Issues +Package: SPL related
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Mar 28 20:01:28 2024 UTC