Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does pcntl_fork() copy PHP objects?

Tags:

php

fork

pcntl

The manual for pcntl_fork() says:

The pcntl_fork() function creates a child process that differs from the parent process only in its PID and PPID.

However, running this simple test surprised me:

class Foo
{
    public function bar()
    {
        if (pcntl_fork()) {
            echo spl_object_hash($this), PHP_EOL;
        } else {
            echo spl_object_hash($this), PHP_EOL;
        }
    }
}

(new Foo)->bar();

The result looks like:

000000005ec7fd31000000003f0fcfe6
000000006b4cd5fc000000007fee8ab7

From what the documentation says, I would have expected the parent and the child to share the same variables, and in particular, when fork()ed from within an object, I would have expected the reference to the object to be the same in both processes. But the example above shows they're not.

Interesting to note, there is no cloning happening here, it looks like the object is just copied. If I add a __clone() function, I can see it's not called during the forking.

Any reason why the variables/objects are not shared by both processes, or any good reading on the subject folks?

like image 855
BenMorel Avatar asked Oct 21 '22 13:10

BenMorel


2 Answers

The reference to the object is the same in the forked process, because the memory location of the object in the child process's memory space is the same.

The hash is calculated as the object address XOR a random mask (which is generated only once) , as you can read in the PHP source code, ext/spl/php_spl.c:

PHPAPI void php_spl_object_hash(zval *obj, char *result TSRMLS_DC) /* {{{*/
{
    intptr_t hash_handle, hash_handlers;
    char *hex;

    if (!SPL_G(hash_mask_init)) {
        if (!BG(mt_rand_is_seeded)) {
            php_mt_srand(GENERATE_SEED() TSRMLS_CC);
        }    

        SPL_G(hash_mask_handle)   = (intptr_t)(php_mt_rand(TSRMLS_C) >> 1);
        SPL_G(hash_mask_handlers) = (intptr_t)(php_mt_rand(TSRMLS_C) >> 1);
        SPL_G(hash_mask_init) = 1; 
    }    

    hash_handle   = SPL_G(hash_mask_handle)^(intptr_t)Z_OBJ_HANDLE_P(obj);
    hash_handlers = SPL_G(hash_mask_handlers)^(intptr_t)Z_OBJ_HT_P(obj);

    spprintf(&hex, 32, "%016x%016x", hash_handle, hash_handlers);

    strlcpy(result, hex, 33); 
    efree(hex);
}
/* }}} */

If the random number generator was seeded before the function was called you would get the exact same output for both the child and the parent process. But in this case it isn't, and each process calculates it own seed. The code for GENERATE_SEED goes:

#ifdef PHP_WIN32
#define GENERATE_SEED() (((long) (time(0) * GetCurrentProcessId())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C))))
#else
#define GENERATE_SEED() (((long) (time(0) * getpid())) ^ ((long) (1000000.0 * php_combined_lcg(TSRMLS_C))))
#endif

As you can see, the seed depends on the process ID, which is of course different for the parent and the child.

So, different random number generator seed, different random mask, different hash.

like image 82
Joni Avatar answered Oct 27 '22 11:10

Joni


The object hash will not being calculated when the object is created (as one could think). The object hash will be calculated when spl_object_hash() is called the first time for the object. This is after fork in your example.

Further note that for the calculation of the hash some randomness is used, therefore the different hashes.

like image 23
hek2mgl Avatar answered Oct 27 '22 09:10

hek2mgl