Skip to content
This repository has been archived by the owner on Dec 16, 2019. It is now read-only.

memory errors with Stackables objects and magic function __call() #389

Closed
simllll opened this issue Dec 2, 2014 · 4 comments
Closed

memory errors with Stackables objects and magic function __call() #389

simllll opened this issue Dec 2, 2014 · 4 comments

Comments

@simllll
Copy link

simllll commented Dec 2, 2014

I have a xml and construct a lot of classes out of it which have references to itself. The classes are just simple class objects with protected variables, using the magic php function I set and get variables from it.

e.g. class WmInstance extends AbstractClass
{
protected $wmInstance; // primary
protected $instanceId; // string
protected $auditTime; // string
}

the AbstractClass has the magic functions in it.

As soon as I extend the AbstractClass with "Stackable" (accesing the object in a non parallel way though - well it crashes also in a parallel way, but to keep things simple I tried to narrow it down), execution stack gets corrupted. It keeps crashing with erros like this one:

Program received signal SIGSEGV, Segmentation fault.
0x000000000092eeb5 in zend_hash_destroy (ht=0x7ffff7f10ee8) at /home/adminuser/Downloads/php-5.6.3/Zend/zend_hash.c:546
546                     p = p->pListNext;

---------------------------------------
[Tue Dec  2 09:45:38 2014]  Script:  '-'
---------------------------------------
/home/adminuser/Downloads/php-5.6.3/Zend/zend_hash.c(553) : Block 0x0198b150 status:
Invalid pointer: ((thread_id=0xF7FD8600) != (expected=0xAC7D0700))
---------------------------------------
[Tue Dec  2 09:45:38 2014]  Script:  '-'
---------------------------------------
/home/adminuser/Downloads/php-5.6.3/Zend/zend_hash.c(556) : Block 0x017cbdf8 status:
Invalid pointer: ((thread_id=0xF7FD8600) != (expected=0xAC7D0700))
---------------------------------------
[Tue Dec  2 09:45:38 2014]  Script:  '-'
---------------------------------------
/home/adminuser/Downloads/php-5.6.3/Zend/zend_variables.c(46) : Block 0x0147ae30 status:
Invalid pointer: ((thread_id=0xF7FD8600) != (expected=0xAC7D0700))
---------------------------------------

(tested on Windows and Linux, php 5.6.3, pthreads 2.0.10)

I think the issue lies somewhere in the attribute saving mechanism of threaded objects.. while narrowing down a testcase I found out it really depends on the object's attribute names (e.g. protected $a works, $protected $aaaaaa does not).

Here is an example code that crashes on windows&linux:

<?php

class AbstractClass extends Stackable
{
    public function __call($name, $arguments)
    {
        $met = substr($name,0,3);
        $fct = lcfirst(substr($name,3));

        $vars = array_keys(get_object_vars($this));
        if(in_array($fct,$vars)===false)
        {
            throw new Exception($fct. " not found as member variable of ".get_class($this));
        }

        switch($met)
        {
            case 'get':
                return $this->$fct;
                break;
            case 'set':
                $this->$fct = $arguments[0];
                break;
            default:
                throw new Exception("INVALID call: ".$name." (".implode(",",$arguments).")");
        }
    }
}

class A extends AbstractClass {
    protected $stringInstanceId;
    protected $numberAuditTime;
    protected $objectB;
    protected $someValue1aaaabbbbbbbba; 
    protected $someValue2; 
    protected $someValue3;
    protected $someValue4;
    protected $someValue5;
    protected $customId; 
    protected $asdfghjklasdfgasdfsdfdsf;
    protected $aaaaaaaaaaaaaaaaaa; 

}

class B extends AbstractClass {
    protected $numberAuditTime;
    protected $objectA;
}

function dosomething()
{
    $a = new A();
    $a->setStringInstanceId("42424610-2f56-11e4-915c-b76a99deb279");
    $a->setNumberAuditTime(1409300825.4619999);

    $b = new B();
    $b->setNumberAuditTime(1409300832.0280001);
    $b->setObjectA($a);

    return $a;
}

for($i=0;$i<10;$i++)
{
    $all = dosomething();
    echo "good ".$i."\n";   
}

die("done\n");

Try changing the protected member variable names and sometimes the crash disappears (it could be related to the length, but not sure..couldn't figure it out). Also it only happens on multiple calls of "dosomething()". First run seems always fine.
If you remove "extends Stackable" from AbstractClass, no php errors can be tracked either.

Let me know if I can provide something else to track this bug down.

Thanks
Simon

@simllll
Copy link
Author

simllll commented Dec 2, 2014

well, i just saw a commit about magic methods.. and if I overwrite the setters explicity like this, things start working:

class A extends AbstractClass {
    protected $stringInstanceId;
    protected $numberAuditTime;
    protected $objectB;
    protected $someValue1aaaabbbbbbbba; 
    protected $someValue2; 
    protected $someValue3;
    protected $someValue4;
    protected $someValue5;
    protected $customId; 
    protected $asdfghjklasdfgasdfsdfdsf;
    protected $aaaaaaaaaaaaaaaaaa; 

    public function setStringInstanceId($str)
    {
        $this->stringInstanceId = $str;
    }

    public function setNumberAuditTime($t)
    {
        $this->numberAuditTime = $t;
    }

}

class B extends AbstractClass {
    protected $numberAuditTime;
    protected $objectA;

    public function setNumberAuditTime($t)
    {
        $this->numberAuditTime = $t;
    }
}

So the issue here is definitly the magic method __call .. any idea how I can still use this way of setting and getting member variables? Or should I just use "__set" and "__get" instead and wait for the next release?

regards
Simon

@simllll simllll changed the title memory errors with Stackables objects,even without thread access (member variables too long?) memory errors with Stackables objects and magic function __call() Dec 2, 2014
@krakjoe
Copy link
Owner

krakjoe commented Dec 2, 2014

I will do some testing with __call, looks like a bug ... we utilize internal call handlers to implement protected and private method calls ... there might be a way to solve it.

For the moment __get and __set should be fine if you are using code from master of github, the magic stuff is unreleased at the moment.

Might be the weekend before I have a chance to look in detail, sorry about that ...

@simllll
Copy link
Author

simllll commented Dec 3, 2014

thanks, no problem so far.. I implemented all the setters and getters for now. Even though the script runs till the end, it gets some memory errors while deconstructing objects if it runs a bit longer:

[Wed Dec  3 03:22:20 2014]  Script:  '-'
---------------------------------------
/home/adminuser/Downloads/php-5.6.3/Zend/zend_hash.c(553) : Block 0x01ead900 status:
Invalid pointer: ((thread_id=0x738D1600) != (expected=0x6590D700))
---------------------------------------
[Wed Dec  3 03:22:20 2014]  Script:  '-'
---------------------------------------
/home/adminuser/Downloads/php-5.6.3/Zend/zend_variables.c(188) : Block 0x7fa2738323a0 status:
/home/adminuser/Downloads/php-5.6.3/Zend/zend_execute.h(80) : Actual location (location was relayed)
Invalid pointer: ((thread_id=0x738D1600) != (expected=0x6590D700))
---------------------------------------

I played around a bit with my first test script to see if I can reconstruct it there too and even though the script is a bit longer now, I "proudly" present an example code that crashes (windows&linux again):

<?php

class AbstractClass extends Stackable
{
    public function compare($obj)
    {
        //return $this->compareByMemberAccess($obj);
        return $this->compareByGetterAccess($obj); // no change :(, still crashes
    }

    public function compareByMemberAccess($obj) // compares member variables to each other (using direct member access)
    {
        if(get_class($obj) != get_class($this)) return false;
        try
        {   
            foreach(array_keys(get_object_vars($this)) as $elm)
            {
                if($this->$elm != null && $obj->$elm != null && $this->$elm instanceof AbstractClass)
                {
                    if(!$this->$elm->compare($obj->$elm))
                    {
                        return false;
                    }
                }
                elseif($this->$elm != $obj->$elm)
                {
                    return false;
                }
            }
            return true;
        }
        catch(Exception $e)
        {
            return false;
        }
    }

    public function compareByGetterAccess($obj) // compares member variables to each other (using getters)
    {
        if(get_class($obj) != get_class($this)) return false;
        try
        {   
            foreach(array_keys(get_object_vars($this)) as $elm)
            {
                $getter = "get".ucfirst($elm);
                if($this->$getter() != null && $obj->$getter() != null && $this->$getter() instanceof AbstractClass)
                {
                    if(!$this->$getter()->compare($obj->$getter()))
                    {
                        return false;
                    }
                }
                elseif($this->$getter() != $obj->$getter())
                {
                    return false;
                }
            }
            return true;
        }
        catch(Exception $e)
        {
            return false;
        }
    }
}

class A extends AbstractClass {
    protected $numberX;
    //somehow I only get a segfault if I add these member variables below - otherwise just memeory errors with invalid pointer, but no total crash (but maybe this was random)
    protected $stringInstanceId;
    protected $numberAuditTime;
    protected $objectB;
    protected $someValue1aaaabbbbbbbba; 
    protected $someValue2; 
    protected $someValue3;
    protected $someValue4;
    protected $someValue5;
    protected $customId; 
    protected $asdfghjklasdfgasdfsdfdsf;
    protected $aaaaaaaaaaaaaaaaaa; 

    public function getStringInstanceId() 
    {
        return $this->stringInstanceId;
    }

    public function setNumberX($x) { $this->numberX = $x; }
    public function getNumberX() { return $this->numberX; }
    public function getNumberAuditTime() { return $this->numberAuditTime; }
    public function getObjectB() { return $this->objectB; }
    public function getSomeValue1aaaabbbbbbbba() { return $this->someValue1aaaabbbbbbbba; }
    public function getSomeValue2()    {        return $this->someValue2;    }
    public function getSomeValue3()    {        return $this->someValue3;    }
    public function getSomeValue4()    {        return $this->someValue4;    }
    public function getSomeValue5()     {         return $this->someValue5;     }
    public function getCustomId()     {         return $this->customId;    }
    public function getAsdfghjklasdfgasdfsdfdsf()     {        return $this->asdfghjklasdfgasdfsdfdsf;    }
    public function getAaaaaaaaaaaaaaaaaa() {  return $this->aaaaaaaaaaaaaaaaaa;     }
}

class B extends AbstractClass {
    protected $numberX;
    protected $numberAuditTime;
    protected $objectA;

    public function setObjectA($a) { $this->objectA = $a; }
    public function getObjectA()    {       return $this->objectA;  }
    public function setNumberX($x)  {       $this->numberX = $x;    }
    public function getNumberX()    {       return $this->numberX;  }
    public function getNumberAuditTime()    {       return $this->numberAuditTime;  }
}

function dosomething()
{
    static $allObjs=array();
    $list = array();

    for($i=0;$i<10;$i++)
    {
        $a = new A();

        $a->setNumberX(rand(0,50));
        $found=false;
        foreach($allObjs as $o)
        {
            if($o->compare($a))
            {
                $a = $o;
                $found = true;
                //echo "using copy (a)\n";
                break;
            }
        }
        if(!$found) $allObjs[] = $a;
        $list[] = $a;

        $b = new B();
        $b->setNumberX(rand(0,50));
        $b->setObjectA($a);
        $found=false;
        foreach($allObjs as $o)
        {
            if($o->compare($b))
            {
                $b = $o;
                $found = true;
                //echo "using copy (b)\n";
                break;
            }
        }
        if(!$found) $allObjs[] = $b;
        $list[] = $b;
    }

    return $list;
}

class TestThread extends Collectable
{
    private $l;
    public function __construct($list)
    {
        $this->l = $list;
    }

    public function run() 
    {
        //usleep(rand(500,100000));
        $l = $this->l;
        foreach($l as $o)
        {
            echo get_class($o).";";
        }
        $this->setGarbage();
    }
}

$pool = new Pool(10,'Worker'); // even more error messages, if pool size is equal to submitted amount of threads
//$pool = new Pool(4,'Worker'); 
for($i=0;$i<10;$i++)
{
    $all[$i] = dosomething();

    $pool->submit(new TestThread($all[$i]));
        /** no change if using stackable instead of an array 
        * $data = new Stackable();
    * foreach($all[$i] as $d)
    * {
    *   $data[] = $d;
    * }
    * $pool->submit(new TestThread($data));*/
    echo "good ".$i."\n";   
}

$pool->shutdown();

die("done\n");
---------------------------------------
[Wed Dec  3 03:58:32 2014]  Script:  '-'
---------------------------------------
/home/adminuser/Downloads/php-5.6.3/Zend/zend_variables.c(46) : Block 0x7f77daf695a0 status:
Invalid pointer: ((thread_id=0xDB01F600) != (expected=0xB3FFF700))
---------------------------------------
[Wed Dec  3 03:58:32 2014]  Script:  '-'
---------------------------------------
/home/adminuser/Downloads/php-5.6.3/Zend/zend_variables.c(188) : Block 0x7f77daf69410 status:
/home/adminuser/Downloads/php-5.6.3/Zend/zend_execute.h(80) : Actual location (location was relayed)
Invalid pointer: ((thread_id=0xDB01F600) != (expected=0xB3FFF700))
---------------------------------------
Segmentation fault (core dumped)

In Windows it's a zend_mm_heap corrupted error. Sometimes you have to execute the script a few times to get the error (based on rand - couldn't find time to narrow it down, the only thing I discovered so far is, that it's has nothing to do with the member variables like in the initial bug report)
-> catch up this bug at #391, so that this bug here is only related to the __call bug.

A big thanks for the extension, looking forward to see the last small memory issues fixed :)

regards
Simon

krakjoe added a commit that referenced this issue Aug 21, 2015
@krakjoe
Copy link
Owner

krakjoe commented Aug 21, 2015

Execution without problem in 7:

<?php

class AbstractClass extends Threaded
{
    public function __call($name, $arguments)
    {
    $met = substr($name,0,3);
    $fct = lcfirst(substr($name,3));

    $vars = array_keys(get_object_vars($this));
    if(in_array($fct,$vars)===false)
    {
        throw new Exception($fct. " not found as member variable of ".get_class($this));
    }

    switch($met)
    {
        case 'get':
            return $this->$fct;
            break;
        case 'set':
            $this->$fct = $arguments[0];
            break;
        default:
            throw new Exception("INVALID call: ".$name." (".implode(",",$arguments).")");
    }
    }
}

class A extends AbstractClass {
    protected $stringInstanceId;
    protected $numberAuditTime;
    protected $objectB;
    protected $someValue1aaaabbbbbbbba; 
    protected $someValue2; 
    protected $someValue3;
    protected $someValue4;
    protected $someValue5;
    protected $customId; 
    protected $asdfghjklasdfgasdfsdfdsf;
    protected $aaaaaaaaaaaaaaaaaa; 

}

class B extends AbstractClass {
    protected $numberAuditTime;
    protected $objectA;
}

function dosomething()
{
    $a = new A();
    $a->setStringInstanceId("42424610-2f56-11e4-915c-b76a99deb279");
    $a->setNumberAuditTime(1409300825.4619999);

    $b = new B();
    $b->setNumberAuditTime(1409300832.0280001);
    $b->setObjectA($a);

    return $a;
}

for($i=0;$i<10;$i++)
{
    $all = dosomething();
    echo "good ".$i."\n";   
}

die("done\n");

Sorry it took so long, and sorry it's not the version you are probably using right now, but gotta look forward to 7 ;)

@krakjoe krakjoe closed this as completed Aug 21, 2015
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants