Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Symfony worker (for inclusion in the wiki) #42

Closed
dunglas opened this issue Sep 30, 2018 · 28 comments
Closed

Symfony worker (for inclusion in the wiki) #42

dunglas opened this issue Sep 30, 2018 · 28 comments

Comments

@dunglas
Copy link
Contributor

dunglas commented Sep 30, 2018

Hi, thanks for this very interesting project!

Here is a worker to serve apps based on the default Symfony skeleton:

<?php
// worker.php
// Install the following mandatory packages:
// composer req spiral/roadrunner symfony/psr-http-message-bridge

ini_set('display_errors', 'stderr');

use App\Kernel;
use Spiral\Goridge\StreamRelay;
use Spiral\RoadRunner\PSR7Client;
use Spiral\RoadRunner\Worker;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Component\Debug\Debug;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\HttpFoundation\Request;

require 'vendor/autoload.php';

// The check is to ensure we don't use .env in production
if (!isset($_SERVER['APP_ENV']) && !isset($_ENV['APP_ENV'])) {
    if (!class_exists(Dotenv::class)) {
        throw new \RuntimeException('APP_ENV environment variable is not defined. You need to define environment variables for configuration or add "symfony/dotenv" as a Composer dependency to load variables from a .env file.');
    }
    (new Dotenv())->load(__DIR__.'/.env');
}

$env = $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? 'dev';
$debug = (bool) ($_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? ('prod' !== $env));

if ($debug) {
    umask(0000);

    Debug::enable();
}

if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
    Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
}

if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) {
    Request::setTrustedHosts(explode(',', $trustedHosts));
}

$kernel = new Kernel($env, $debug);
$relay = new StreamRelay(STDIN, STDOUT);
$psr7 = new PSR7Client(new Worker($relay));
$httpFoundationFactory = new HttpFoundationFactory();
$diactorosFactory = new DiactorosFactory();

while ($req = $psr7->acceptRequest()) {
    try {
        $request = $httpFoundationFactory->createRequest($req);
        $response = $kernel->handle($request);
        $psr7->respond($diactorosFactory->createResponse($response));
        $kernel->terminate($request, $response);
        $kernel->reboot(null);
    } catch (\Throwable $e) {
        $psr7->getWorker()->error((string)$e);
    }
}

And the corresponding .rr.yaml file:

http:
  address: 0.0.0.0:8080
  workers:
    command: "php test-sf/worker.php"
    pool:
      numWorkers: 4

static:
  dir:   "test-sf/public"
  forbid: [".php", ".htaccess"]

Would you mind including it in the wiki?

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 1, 2018

Done! https://github.com/spiral/roadrunner/wiki/Symfony-Framework Let me know if you want to change something in the article. I wonder if GitHub has any protocol for Wiki PRs.

@wolfy-j wolfy-j closed this as completed Oct 1, 2018
@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 1, 2018

FYI, you can define ENV in env section of config, these variables would be PHP process specific.

@dunglas
Copy link
Contributor Author

dunglas commented Oct 1, 2018

Thanks @wolfy-j. Indeed env vars should probably be moved here. I'll send you the changes when ready.

@mnavarrocarter
Copy link

@dunglas I was using your config in my SF 4.1 app, but my static files like (app.js and images) could not be loaded. Switched to php -S 0.0.0.0:8000 -t public/ and worked. ¿Do you have any idea or suggestions for finding the issue?

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 2, 2018

Have you changed static.dir option to reflect your directory structure?

static:
  dir:   "public"
  forbid: [".php", ".htaccess"]

Also, try to run with -d flag to see what RR debug log says. Most likely your public directory path is invalid (though you must get an error if the path does not exist).

@mnavarrocarter
Copy link

Yeap, the folder is pointing to the right direction and exists. Running with -d flag shows no output. The error happens at browser level when tries to load the js.

image

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 2, 2018

Does -d show any other requests coming to RR?

It should look like that: http://prntscr.com/l1atdz

@mnavarrocarter
Copy link

Yeap, and found the assets ones. They are answered with 404s.

image

@mnavarrocarter
Copy link

Yeah, found it. Is passing the requests for files directly to the application.

@mnavarrocarter
Copy link

Daaang I'm stupid! The static config was not properly indented.

Sorry for wasting your time @wolfy-j . I'm loving the project already!! :)

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 2, 2018

This is correct behavior if file not found or static service is disabled (identical to nginx try_files).

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 2, 2018

Daaang I'm stupid! The static config was not properly indented.

Sorry for wasting your time @wolfy-j . I'm loving the project already!! :)

Ugh, yaml is really annoying sometimes. Try using json config instead if you run into this problem often. :)

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

I just tried this example, but it doesn't work. I get this error: worker error: invalid prefix (checksum)
This is shown in my PHP error output:

[05-Oct-2018 09:50:32 UTC] PHP Fatal error:  Uncaught ErrorException: Warning: unpack(): Type C: not enough input, need 1, have 0 in /var/www/app/vendor/spiral/goridge/php-src/StreamRelay.php:110
Stack trace:
#0 /var/www/app/vendor/spiral/goridge/php-src/StreamRelay.php(76): Spiral\Goridge\StreamRelay->fetchPrefix()
#1 /var/www/app/vendor/spiral/roadrunner/src/Worker.php(51): Spiral\Goridge\StreamRelay->receiveSync(NULL)
#2 /var/www/app/vendor/spiral/roadrunner/src/Worker.php(56): Spiral\RoadRunner\Worker->receive(Array)
#3 /var/www/app/vendor/spiral/roadrunner/src/PSR7Client.php(45): Spiral\RoadRunner\Worker->receive(Array)
#4 /var/www/app/worker.php(51): Spiral\RoadRunner\PSR7Client->acceptRequest()
#5 {main}
  thrown in /var/www/app/vendor/spiral/goridge/php-src/StreamRelay.php on line 110

When I change the while loop to this:

while ($req = $psr7->acceptRequest()) {
    try {
        $resp = new \Zend\Diactoros\Response();
        $resp->getBody()->write("hello world");

        $psr7->respond($resp);
    } catch (\Throwable $e) {
        $psr7->getWorker()->error(get_class($e));
        $psr7->getWorker()->error((string)$e);
    }
}

it works fine.

Something is happening inside $response = $kernel->handle($request); that breaks.

What am I doing wrong?

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 5, 2018

Do you echo anything to STDOUT without buffers? There is an option how you can debug it.

Switch RR to work over TCP:

workers:
    relay: "tcp://localhost:5000"

Use SocketRelay instead of SteamRelay:

$relay = new SocketRelay("localhost", 5000);

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

Weird, now it works. But I don't see anything being echo'd?

DEBU[0000] [rpc]: started
DEBU[0000] [http]: started
INFO[0002] 172.18.0.1 302 GET http://my-domain.eu.ngrok.io/
INFO[0004] 172.18.0.1 302 GET http://my-domain.eu.ngrok.io/
INFO[0009] 172.18.0.1 200 GET http://my-domain.eu.ngrok.io/_profiler/empty/search/results?limit=10
INFO[0013] 172.18.0.1 200 GET http://my-domain.eu.ngrok.io/_profiler/df487a
INFO[0017] 172.18.0.1 200 GET http://my-domain.eu.ngrok.io/cart/1
INFO[0022] 172.18.0.1 200 GET http://my-domain.eu.ngrok.io/cart/1
INFO[0023] 172.18.0.1 200 GET http://my-domain.eu.ngrok.io/cart/1
INFO[0025] 172.18.0.1 302 GET http://my-domain.eu.ngrok.io/_profiler/search?limit=10
INFO[0025] 172.18.0.1 200 GET http://my-domain.eu.ngrok.io/_profiler/d179e9/search/results?ip=&limit=10
INFO[0027] 172.18.0.1 200 GET http://my-domain.eu.ngrok.io/_profiler/d179e9
INFO[0030] 172.18.0.1 200 GET http://my-domain.eu.ngrok.io/_profiler/d179e9?panel=time

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 5, 2018

Well, RR does not redirect STDOUT to the log, only STDERR. Does your worker die during the bootload? If so, try to run it manually php worker.php and see what it echoes and where.

If you can't find it (or don't want to) switch to TCP or Unix sockets (unix://socket.sock). It would work just a bit slower than pipes and will allow you to use STDOUT for your own purposes.

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

I started RR and in a new terminal opened php worker.php but it didn't output anything. It's just waiting... Also tried to move the SF kernel->boot to outside the while loop. No difference.

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

When doing a couple requests, and monitor rr http:workers it stays up. So the worker doesn't die.

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 5, 2018

So it's not bootloader issues, sounds like you output something to STDOUT while running your script. Is it possible you have some warnings and display_errors points to STDOUT?

Since it's working over sockets I can conclude that your workers are fine, but they definitely use pipes for something which creates conflicts with Goridge relays.

P.S. Warning: unpack(): Type C: not enough input, need 1, have 0 in /var/www/app/vendor/spiral/goridge/php-src/StreamRelay.php:110 this error is expected if you start your worker manually without properly formatting your data frames.

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 5, 2018

Do you use any header functions? They will write to STDOUT as well.

@wolfy-j
Copy link
Contributor

wolfy-j commented Oct 5, 2018

More info added here: https://github.com/spiral/roadrunner/wiki/PHP-Workers

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

I don't have any header functions in use. Also checked output of headers_list() and its empty. Is there a way of capturing the output of the php worker.php file?

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

I'm running this inside Docker btw, not sure if that's related.

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

Problem solved:

env(LOG_STREAM): 'php://stdout'

Sorry everybody!

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

The Symfony example is not performant at all. It's doing a $kernel->reboot(null); after every request. Is that really necessary? Why not just do $kernel->boot(); before the while loop, and then process all the requests without rebooting? Makes it much faster.

@dunglas
Copy link
Contributor Author

dunglas commented Oct 5, 2018

Yes it is, some Symfony services are stateful. You must reset their states before handling a different requests to avoid side effects.

@ruudk
Copy link
Contributor

ruudk commented Oct 5, 2018

Wasn't this fixed with resettable services?

@OO00O0O
Copy link

OO00O0O commented Dec 20, 2018

PM has a lot of reseting going on: https://github.com/php-pm/php-pm-httpkernel/blob/master/Bootstraps/Symfony.php

With reset tag in symfony now, this should be a lot smaller.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants