3

I've searched EVERYWHERE for this and I'm having lots of problems, But I don't think it's a problem with the actual code. Basically this code starts the socket server(login and game) in two separate threads, I basically converted this code from a non-thread version but I've been unable to get this working for threads.

include "socket.php";
include "dep.php";

class Server extends Thread {
    public $server;
    public $config;
    public function __construct($type){
        //$this->config = (string)$type;
        $this->run2($type);
        $this->run();
    }
    public function run(){
        while(true){
            $this->server->loop();
        }
    }
    public function run2($config){
        $this->server = new sokserv($config);
        $this->server->init();
        //while(true){
        //  $this->server->loop();
        //}
    }
}
$login = new Server('Config/config.xml');
$game = new Server("Config/config2.xml");
The error received is 
Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'SimpleXMLElement' is not allowed' in C:\Users\=\Desktop\Test\Start.php:19
Stack trace:
#0 C:\Users\=\Desktop\Test\Start.php(19): Server->run2()
#1 C:\Users\=\Desktop\Test\Start.php(10): Server->run2('Config/config.x...')
#2 C:\Users\=\Desktop\Test\Start.php(26): Server->__construct('Config/config.x...')
#3 {main}
  thrown in C:\Users\=\Desktop\Test\Start.php on line 19

However, reverting to the old code works fine. The first bit of sokserv(I removed some public vars because they contain personal info)

class sokserv
{
    public $ip;
    public $port;
    public $users = array();
    public $config;
    public $mysql;
    private $socket;
    public function __construct($config = "Config/config.xml")
    {
        $this->readConfig($config);
    }
    public function readConfig($file)
    {
        if (!file_exists($file))
            die("Could not find config");
        $this->config = simplexml_load_file($file);
    }

And if you wanted this is the xml:

<server>
    <port>6112</port>
    <ip>0</ip>
    <mysql>
        <host>127.0.0.1</host>
        <username>root</username>
        <dbname></dbname> 
        <password></password>
        <table></table>
    </mysql>
</server>
Dan Lugg
  • 20,192
  • 19
  • 110
  • 174
  • Please share the sockserv implementation or source code link. I think you may need to convert the config var to array. – Filip Górny Jan 16 '14 at 23:00
  • I'm unable to share the sokserv class as It's too big and contains personal information, I can however share the first few bits that involve config. – user2601312 Jan 16 '14 at 23:14
  • yes pleas do so, you may share just a constructor – Filip Górny Jan 16 '14 at 23:18
  • I've edited the question, I've also added the xml. – user2601312 Jan 16 '14 at 23:19
  • I'm sorry I didn't notice it is related to Start.ph file. Please see this http://stackoverflow.com/questions/14912551/serialization-of-simplexmlelement-is-not-allowed-when-saving-in-wordpress-pos – Filip Górny Jan 16 '14 at 23:26
  • I'm confused as to what I have to change :( – user2601312 Jan 16 '14 at 23:30
  • It would be useful if you could provide a working example of what you want to do. Nobody can test code they cannot run. You are writing unserializable objects to the object context, don't do that. Instead pass it around, manipulate it so that it is in a format that is compatible (like an object descending from pthreads) and write that the object context (they aren't serialized). If you'd like code, post a full example. On a side note, it's not a good idea to store personal information in any source files. – Joe Watkins Jan 17 '14 at 12:58
  • @JoeWatkins I've reverted your edit to my answer because it was basically a completely different answer. Please add it as such, and I will either withdraw mine or leave it as a more general explanation. – IMSoP Jan 18 '14 at 13:19

2 Answers2

8

The Zend memory manager, with the help of TSRM, are purposefully built to prohibit contexts from sharing data; no context can allocate or free anything in another thread. This is referred to as a shared nothing architecture and it does exactly what it says on the tin. The job of pthreads is to breach that barrier in a safe, sane manner.

Obviously, none of the extensions bundled with PHP are aware of pthreads, nor do we want them to be, for they would just become vastly complicated. When it comes to objects provided by other extensions, pthreads makes the assumption that the safest thing to do is serialize the object for storage. Some objects prohibit this from occurring, SimpleXML descendants are one such group of objects.

Strings, floats, integers and objects that descend from pthreads are not serialized. Objects that descend from pthreads hi-jack the serialization API, storing the physical addressof the object, avoiding serialization by directly accessing the thread safe structure representing the object in userland.

The proper solution is to wrap the data you wish to share in an object descended from pthreads:

<?php
class Config extends Stackable {
    /**
    * Constructs thread safe, sharable configuration from complex XML
    * @param mixed $xml         SimpleXMLElement or location of XML file
    * @param array &$objects    reference store
    */
    public function __construct($xml, &$objects) {
        if ($xml instanceof SimpleXMLElement) {
            foreach ($xml as $key => $value)
                $this[$key] = (string) $value;
        } else {
            foreach (simplexml_load_string(
                        $xml) as $key => $value) {
                if ($value->children()) {
                    $this[$key] = new Config($value, $objects);
                } else $this[$key] = (string) $value;
            }
        }

        /* maintain object references */
        $objects[] = $this;
    }

    public function run() {}
}

class Test extends Thread {
    protected $config;

    /**
    * Constructs thread using pre-constructed thread safe, shared configuration object
    * @param Config $config
    */
    public function __construct(Config $config) {
        $this->config = $config;
    }

    public function run() {
        /* iterate over configuration */
        printf("%d settings:\n", count($this->config));
        foreach ($this->config as $key => $data) {
            if (count($data) > 1) {
                printf( 
                    "\t%s, %d settings:\n", $key, count($data));
                foreach ($data as $name => $value) {
                    printf("\t\t%s = %s\n", $name, $value);
                }
            } else printf("\t%s = %s\n", $key, $data);
        }
        printf("\n");

        printf(
            "Host: %s:%d\n", 
            $this->config->ip, 
            $this->config->port);

        printf(
            "MySQL: %s@%s?database=%s&password=%s&table=%s\n", 
            $this->config->mysql->username,
            $this->config->mysql->host,
            $this->config->mysql->dbname,
            $this->config->mysql->password,
            $this->config->mysql->table);
    }
}

/* Example XML */
$xml = <<<XML
<server>
    <port>6112</port>
    <ip>some.ip</ip>
    <mysql>
        <host>127.0.0.1</host>
        <username>root</username>
        <dbname>somedb</dbname> 
        <password>somepass</password>
        <table>sometable</table>
    </mysql>
</server>
XML;

/* Object reference storage */
$objects = [];
$config = new Config($xml, $objects);

$thread = new Test($config);
$thread->start();

$thread->join();
?>

Will output the following:

3 settings:
        port = 6112
        ip = some.ip
        mysql, 5 settings:
                host = 127.0.0.1
                username = root
                dbname = somedb
                password = somepass
                table = sometable

Host: some.ip:6112
MySQL: root@127.0.0.1?database=somedb&password=somepass&table=sometable

The example provided uses the [format] XML you provided in the question, it takes that XML and creates a thread safe representation of it, which will never be serialized.

The logic in the constructor of Config wholly depends upon the format of the XML you are using.

You can pass that Config object to as many threads as you wish, all of them can read/write properties, and execute it's methods.

All data that you intend to share should be managed in this way, what you want to take away from this is not that you should work around the exception and try to store serial data, but rather that you should create suitable containers for your data that actually support, properly, multi-threading.

Further reading: https://gist.github.com/krakjoe/6437782

Joe Watkins
  • 17,032
  • 5
  • 41
  • 62
  • This answer isn't entirely clear to me: you say "an object descended from pthreads", but the example actually descends from a class `Stackable`, which apparently has some `ArrayAccess` magic, since you then do `$this[$key]`. A link to full documentation on that might be good. It's also not clear if this could be used to transport the SimpleXML object itself, since here you're just extracting strings from it, which won't work with the XML provided by the OP, and would serialize fine without any special tricks. – IMSoP Jan 18 '14 at 17:01
  • It would serialize, but not be thread safe, so not shared. http://php.net/intro.pthreads – Joe Watkins Jan 18 '14 at 17:10
  • So, the `Stackable` allows the `Config` items to be writeable, but doesn't actually solve the OP's problem of passing a SimpleXML object around? The actual workaround for the SimpleXML serialization issue seems to be this line: `$this[$key] = (string) $value;` – IMSoP Jan 18 '14 at 17:29
  • 1
    That is intractable: http://lxr.php.net/xref/PHP_5_4/ext/simplexml/simplexml.c#2636 The object cannot be stored in it's current form, I provided the correct way to store the data so that it can be shared. – Joe Watkins Jan 18 '14 at 17:37
  • Please correct me if I'm wrong, but it seems that making `sokserv` extend `Stackable`, while probably a good idea, won't actually solve the OP's problem. The solution to the OP's problem is to convert the SimpleXML object into a form which is safe for serialization. Your code does this in a crude loop which will not work for the example XML in the question, and doesn't comment that this is a key part of the solution. – IMSoP Jan 18 '14 at 18:06
  • @IMSoP Hmm ... considering Joe actually *wrote* `ext/pthreads` I feel pretty confident that he's correct about how the extension works. –  Jan 18 '14 at 18:09
  • @rdlowrey I didn't say he was incorrect (although he said I was), I just said he didn't explain it very well. – IMSoP Jan 18 '14 at 18:15
0

Because PHP does not natively support sharing data between threads, the pthreads code is serializing data that needs to be shared between two threads, so it can create a clean copy in another thread when needed (by unserializing it). The exceptions to this are simple scalar types, which can be copied easily anyway, and the Thread, Worker, and Stackable objects which represent the actual tasks being executed, and have explicit support for synchronization.

Unfortunately, some PHP objects do not support serialization, because they are internally implemented as pointers to an in-memory structure, not just a set of PHP variables. SimpleXML is one example of this, hence the error message you are seeing.

There are a few ways around this, depending how "clever" you want to be:

  • The simplest is not to parse the XML in your sokserv->readConfig method, or not to run that method straight away. Instead, you could store the filename, or the raw XML string, and parse it later. Each thread would then be creating its own SimpleXML object explicitly, and only the string would need to be passed between threads.
  • Contrarily, you could parse the XML completely, extract the information you need into a simpler object, and then discard the SimpleXMLElement, all before starting the thread.
  • Another possibility would be to give your sokserv class __sleep and __wakeUp methods, or make it implement the Serializable interface. In both cases, the idea would be to turn $this->config into an XML string on serialization/sleep, and back into a (new) SimpleXML object on unserialization/wake-up.
  • The most advanced approach would be to wrap SimpleXMLElement itself with custom serialization behaviour. Note that a simple sub-class of SimpleXMLElement cannot reimplement the Serializable interface, as shown by this example. You would need instead to put the SimpleXMLElement into some container object or use the Delegation pattern.

Whatever approach you choose, the basic concept is the same: the data being passed between threads (attached to a Thread, Worker, or Stackable object) cannot be of a class such as SimpleXML which cannot be serialized.

IMSoP
  • 89,526
  • 13
  • 117
  • 169
  • @JoeWatkins I've reverted your edit because it was basically a completely different answer. Please add it as such, and I will either withdraw mine or leave it as a more general explanation. – IMSoP Jan 18 '14 at 13:16
  • @JoeWatkins I'd say it's "incomplete" rather than "wrong" - sure, I didn't know that pthreads had an in-built solution for this (I couldn't even tell that the OP was using pthreads), but I correctly diagnosed the problem as objects being serialized to pass between contexts. The solution of wrapping in the special container provided by pthreads is not entirely dissimilar to the solution of wrapping in a container implementing `Serializable`, and your additional information confirms that the solutions here would work fine, even if they're not completely optimal. – IMSoP Jan 18 '14 at 17:06
  • 2
    It's better that there is one correct way to do things, for the sake of education. The things you mentioned are workarounds, at best, and the information incorrect, guesses. There is a built in solution so I showed it, I don't much care for internet points, I just didn't want to confuse what is already a difficult subject to grasp. – Joe Watkins Jan 18 '14 at 17:12
  • @JoeWatkins I'll admit to guessing some of the details, but your answer says substantially the same thing: "When it comes to objects provided by other extensions, pthreads makes the assumption that the safest thing to do is serialize the object for storage". If the use of `Stackable` negates this serialization (which I'm not clear if it does or not) then yes, these are sub-optimal workarounds, but they would meet the OP's need of initialising threads based on an XML config. – IMSoP Jan 18 '14 at 17:52
  • 1
    If you're going to give authoritative answers to questions you should know about the subject matter, guessing is not good enough. As for the rest: RTFM. – Joe Watkins Jan 18 '14 at 18:04
  • 2
    @IMSoP Joe Watkins is the author of pthreads, so my guess is he knows more about it than you. – kittycat Jan 18 '14 at 18:07
  • @crypticツ I realise that, and I suspect the problem is that he knows *too much* about it, so certain things seem self-evident to him which I'm not grasping. I am trying to understand, but have yet to see any evidence that I was "wrong" to see the underlying problem here as pthreads wanting objects it manages to be serializable. (From the manual: "As a rule of thumb, any data type that can be serialized can be used as a member of a Threaded object") – IMSoP Jan 18 '14 at 18:12
  • You most advanced "solution", didn't work, evidence enough for me and should be for you. pthreads doesn't _want_ to serialize anything, what it _wants_ is for you to store data safely in supported objects. That's what the example shows, note that I did ask the OP for his working code so I could write a proper answer, but I didn't have the time to wait for a response before you offerred up an answer. What I wrote will help the OP, what you wrote will not. Apologies if I fail to explain something, try the manual and the examples (on github), or maybe: https://gist.github.com/krakjoe/6437782 – Joe Watkins Jan 18 '14 at 18:35
  • If something really is self evident to me, then there's no point me sitting here trying to work out what is self evident so I can explain it to you. This is what I find myself doing, I'm quite sure that the manual, examples and that article should give anyone a good understand of the hows and whys of pthreads. I don't know what else to say ... – Joe Watkins Jan 18 '14 at 18:48
  • @JoeWatkins I'm sorry, I still don't get it. I get that `Stackable` does magic things to synchronise data, but it still seems to *also* be necessary for that data to either be serializable or a scalar type (which would be the case if the XML was passed in as a string, and parsed later). Nothing in the manual or that gist really addresses this other than that line I quoted already. – IMSoP Jan 18 '14 at 18:51
  • I have re-worded my answer to explicitly include what I have learned today about pthreads. The central approach remains, because nobody has yet explained to me a method that doesn't involve manipulating your data into a scalar or serializable form. – IMSoP Jan 18 '14 at 19:14
  • 1
    I'm glad you learned something :) Now I see what was obvious to me and not you, I have updated my answer too. Please review it, and for the sake of the sanity and education of everyone please remove this answer. – Joe Watkins Jan 19 '14 at 11:46