swoole / swoole-src

🚀 Coroutine-based concurrency library for PHP
https://www.swoole.com
Apache License 2.0
18.44k stars 3.16k forks source link

concurrency issues #1286

Closed tmuehlschlegel closed 7 years ago

tmuehlschlegel commented 7 years ago

amazing work, but swoole is not concurrency aware:

<?php

$server = new Swoole\Http\Server('127.0.0.1', 9500);

$i = 0;

$server->on('Request', function ($request, $response) use (&$i) {
    $response->end(++$i);
});

$server->start();

benchmark the code:

ab -c10 -n1000 http://localhost:9500/

all requests were processed successfully, so the expected result is 1000. But it's much lower:

curl http://localhost:9500 # ~200

test environment:

# php -v
PHP 7.0.5-4+donate.sury.org~xenial+1 (cli) ( ZTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies

# pecl list
Installed packages, channel pecl.php.net:
=========================================
Package          Version State
swoole           2.0.7   beta
swoole_serialize 0.1.1   beta
viaweb3 commented 7 years ago

ps -ef | grep php to see how much worker process you have, each process should handle your request.

tmuehlschlegel commented 7 years ago

I mean swoole isn't atomic. In a normal php environment, the code above should return 1000, because the url was executed 1000 times. With swoole it's executed 1000 times too, but the result is only 200 (vary).

Replace the code above with the code below.

$server = new Swoole\Http\Server('127.0.0.1', 9500);
$file = '/tmp/debug.log';

$i = 0;
@unlink($file);

$server->on('Request', function ($request, $response) use (&$i, $file) {
    file_put_contents($file, '1' . PHP_EOL, FILE_APPEND);
    $response->end((++$i) . ' vs ' . count(file($file)));
});

$server->start();

Then benchmark the server again

ab -c10 -n1000 http://localhost:9500/

Then check the result

curl http://localhost:9500 // output: "215 vs 1001"

See https://en.wikipedia.org/wiki/Race_condition for more background information.

kse300489 commented 7 years ago

@rottenrice your not understand how swoole is working

tmuehlschlegel commented 7 years ago

It's hard to understand something when it's not documented in a language which you understand.

I can only read the code examples, e.g. https://github.com/swoole/swoole-src/blob/master/examples/coroutine/redis_pool.php is very similar to my example above.

Under highload:

I'm not sure if such a behaviour is expected. For me it's a critical bug. For instance that's the reason why you should build php with "thread saftey = On" when you are using pthreads. But here it does not have any positive effect

matyhtf commented 7 years ago

By default, multiple worker processes are started. The $i is a local variable, Memory is quarantined in a multi-process environment. $i++ is only in one work process.

matyhtf commented 7 years ago

Try this program

<?php
$server = new Swoole\Http\Server('127.0.0.1', 9500);

$atomic = new Swoole\Atomic(0);

$server->on('Request', function ($request, $response) use ($atomic) {
    $response->end($atomic->add(1));
});

$server->start();
matyhtf commented 7 years ago

We are writing an English document now. Thanks for your question.

simonhf commented 6 years ago

"By default, multiple worker processes are started"

How to control how many processes are started and possibly limit to just one process? And with one process then would the original script posted by rottenrice work as expected?

simonhf commented 6 years ago

I managed to cut the php processes down from 10 to 3 by configuring work_num to 1 as follows:

$ cat server.php 
<?php

$server = new Swoole\Http\Server('127.0.0.1', 9500);

$server->set([
        'open_tcp_nodelay' => true,
        'worker_num' => 1,
]);

$i = 0;

$server->on('Request', function ($request, $response) use (&$i) {
    $response->end(++$i);
});

$server->start();

After running this script then only 3 php processes are started:

$ ps auxwww | egrep "[p]hp"
simon     32113  0.0  0.7 257424 28856 pts/10   Sl+  14:34   0:00 php server.php
simon     32114  0.0  0.1 183068  5548 pts/10   S+   14:34   0:00 php server.php
simon     32116  0.0  0.2 185580  8156 pts/10   S+   14:34   0:00 php server.php

And when running it then $i ends up at 1,001 as expected:

$ ab -c10 -n1000 http://localhost:9500/ ; echo "single curl request:" ; curl http://localhost:9500 ; echo
...
single curl request:
1001

So this explains how to get the code from @rottenrice working without complicated atomic code etc :-)

However, @matyhtf, obviously with worker_num set to 1 then all the HTTP processing is happening in a single worker process as originally wanted by @rottenrice, but what are the other 2 processes (of the total 3 processes started) for? And is it possible to collapse them into a single process somehow, or to configure them not to start up? For example, with nginx then it's possible to run a single nginx process as a tiny http server. Is it possible to run a swoole HTTP server as a single process too?

Reading the docs then I'm guessing the other 2 processes are the 'master' process and the 'manager' process. How can I tell which process is which type of process in the swoole process model? Is there any documentation on what the swoole master and manager processes do?

simonhf commented 6 years ago

I noticed that while running ab then 2 out of the 3 swoole php processes use CPU:

$ ab -c10 -n10000 http://localhost:9500/ ; echo "single curl request:" ; curl http://localhost:9500 ; echo
...
single curl request:
10001

$ top -d 1 -b | egrep --line-buffered php | perl -lane '$|++; printf qq[%s %s\n], scalar localtime, $_;'
Fri Dec  8 15:13:09 2017  32299 simon     20   0  257424  28728  23156 S   1.0  0.7   0:00.02 php
Fri Dec  8 15:13:09 2017  32300 simon     20   0  183068   5548      0 S   0.0  0.1   0:00.00 php
Fri Dec  8 15:13:09 2017  32302 simon     20   0  185460   9736   4156 S   0.0  0.2   0:00.00 php
Fri Dec  8 15:13:10 2017  32299 simon     20   0  257424  28728  23156 S  15.8  0.7   0:00.18 php
Fri Dec  8 15:13:10 2017  32302 simon     20   0  185460   9736   4156 S   9.9  0.2   0:00.10 php
Fri Dec  8 15:13:10 2017  32300 simon     20   0  183068   5548      0 S   0.0  0.1   0:00.00 php
Fri Dec  8 15:13:11 2017  32299 simon     20   0  257424  28728  23156 S  16.8  0.7   0:00.35 php
Fri Dec  8 15:13:11 2017  32302 simon     20   0  185460   9736   4156 S  10.9  0.2   0:00.21 php
Fri Dec  8 15:13:11 2017  32300 simon     20   0  183068   5548      0 S   0.0  0.1   0:00.00 php
Fri Dec  8 15:13:12 2017  32299 simon     20   0  257424  28728  23156 S  13.9  0.7   0:00.49 php
Fri Dec  8 15:13:12 2017  32302 simon     20   0  185460   9736   4156 S   8.9  0.2   0:00.30 php
Fri Dec  8 15:13:12 2017  32300 simon     20   0  183068   5548      0 S   0.0  0.1   0:00.00 php
Fri Dec  8 15:13:13 2017  32299 simon     20   0  257424  28728  23156 S  13.9  0.7   0:00.63 php
Fri Dec  8 15:13:13 2017  32302 simon     20   0  185460   9736   4156 R   8.9  0.2   0:00.39 php
Fri Dec  8 15:13:13 2017  32300 simon     20   0  183068   5548      0 S   0.0  0.1   0:00.00 php
Fri Dec  8 15:13:14 2017  32299 simon     20   0  257424  28728  23156 S  14.9  0.7   0:00.78 php
Fri Dec  8 15:13:14 2017  32302 simon     20   0  185460   9736   4156 S   9.9  0.2   0:00.49 php
Fri Dec  8 15:13:14 2017  32300 simon     20   0  183068   5548      0 S   0.0  0.1   0:00.00 php
Fri Dec  8 15:13:15 2017  32299 simon     20   0  257424  28728  23156 S   9.9  0.7   0:00.88 php
Fri Dec  8 15:13:15 2017  32302 simon     20   0  185460   9736   4156 S   6.9  0.2   0:00.56 php
Fri Dec  8 15:13:15 2017  32300 simon     20   0  183068   5548      0 S   0.0  0.1   0:00.00 php
Fri Dec  8 15:13:16 2017  32299 simon     20   0  257424  28728  23156 S   0.0  0.7   0:00.88 php
Fri Dec  8 15:13:16 2017  32300 simon     20   0  183068   5548      0 S   0.0  0.1   0:00.00 php
Fri Dec  8 15:13:16 2017  32302 simon     20   0  185460   9736   4156 S   0.0  0.2   0:00.56 php

So this means the listener and receiver is running in one process and the worker is running in another (swoole 'worker'?) process? But how to merge them into a single process?

simonhf commented 6 years ago

Interesting... if I change the following line in the above server.php script then the number of PHP processes drops from 3 to 1:

$server = new Swoole\Http\Server('127.0.0.1', 9500, SWOOLE_BASE);

Note: I believe the default is SWOOLE_PROCESS.