📜 ⬆️ ⬇️

Introduction to the development of web-applications on PSGI / Plack. Part 3. Starman

Continuing a series of articles on PSGI / Plack. Reviewed in more detail preforking Starman PSGI server.

With the permission of the author of the article and the chief editor of the magazine PragmaticPerl.com . The original article is located here.

Starman?


The author of this server (Tatsuhiko Miyagawa) says the following about him:
')
“Starman’s name comes from Star HA Otoko’s song from Japanese rock band Unicorn (Yes, Unicorn). David Bowie also has a song of the same name, Starman is the name of the character of the cult Japanese game Earthbound, the name of the musical theme from the Super Mario Brothers.

I'm tired of naming Perl modules like HTTP :: Server :: PSGI :: How :: Its :: Written :: With :: What :: Module, and as a result people call it HSPHIWWWM in IRC. It is badly pronounced and creates problems for beginners. Yes, maybe I am an uproar. Time will tell."

With the name sorted out. Now we will deal with the server itself.


Preforking?


Starman's preforking model is similar to the most high-performance Unix servers. It uses a model of pre-running processes It also automatically restarts the pool of workers and removes its zombie processes.

Plack application


This time the Plack application will be quite elementary:

use strict; use warnings; use Plack; use Plack::Builder; use Plack::Request; sub body { return 'body'; } sub body2 { return shift; } my $app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->body(body()); return $res->finalize(); }; my $main_app = builder { mount "/" => builder { $app }; }; 

When developing, Starman needs to understand one very important point of its work. Consider, for example, a connection to a database. Very often, in order to save time and lines of code, the connection initialization is brought to the very beginning of the script. This applies to CGI and sometimes FastCGI. In the case of PSGI, this is not possible. And that's why. When the server starts, this code will be executed exactly once for each worker. And the danger of the situation lies in the fact that at first, until the connection takes off either by timeout or for some other reason, the application will work in normal mode. In the case of asynchronous servers, you can initialize the connection pool at the beginning of the application code (connection! = Connection pool).

In order to confirm or deny this, we will make changes to the application code. Add to the beginning of the code, after imports, the following line:

 warn 'AFTER IMPORT'; 


Now the application should look like:

 use strict; use warnings; use Plack; use Plack::Builder; use Plack::Request; warn 'AFTER IMPORT'; sub body { return 'body'; } sub body2 { return shift; } my $app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->body(body()); return $res->finalize(); }; my $main_app = builder { mount "/" => builder { $app }; }; 

For the purity of the experiment, we will launch a starman with one worker at the following command:

 starman --port 8080 --workers 1 app.psgi 


Where app.psgi is an application.

Immediately after the launch, we see the following picture in STDERR:

 noxx@noxx-inferno ~/perl/psgi $ starman --port 8080 app.psgi --workers 1 2013/06/02-15:05:31 Starman::Server (type Net::Server::PreFork) starting! pid(4204) Resolved [*]:8080 to [::]:8080, IPv6 Not including resolved host [0.0.0.0] IPv4 because it will be handled by [::] IPv6 Binding to TCP port 8080 on host :: with IPv6 Setting gid to "1000 1000 4 24 27 30 46 107 125 1000 1001" AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. 

If you send a request to localhost: 8080 /, you can make sure that nothing new in STDERR has appeared, and the server responds normally.
In order to make sure that the worker is really alone, run the following command:

 ps uax | grep starman 


Result:

 noxx 4204 0.6 0.1 57836 11264 pts/3 S+ 15:05 0:00 starman master --port 8080 app.psgi --workers 1 noxx 4205 0.2 0.1 64708 13164 pts/3 S+ 15:05 0:00 starman worker --port 8080 app.psgi --workers 1 noxx 4213 0.0 0.0 13580 940 pts/4 S+ 15:05 0:00 grep --colour=auto starman 

There are two processes. But actually worker from them only one. Let's do another experiment. Run the starman with three workers.

 starman --port 8080 --workers 3 app.psgi 


Result:

 2013/06/02-15:11:08 Starman::Server (type Net::Server::PreFork) starting! pid(4219) Resolved [*]:8080 to [::]:8080, IPv6 Not including resolved host [0.0.0.0] IPv4 because it will be handled by [::] IPv6 Binding to TCP port 8080 on host :: with IPv6 Setting gid to "1000 1000 4 24 27 30 46 107 125 1000 1001" AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. 


That's right. Now look at the process list. I have it looks like this:

 noxx 4219 0.1 0.1 57836 11264 pts/3 S+ 15:11 0:00 starman master --port 8080 app.psgi --workers 3 noxx 4220 0.0 0.1 64460 12756 pts/3 S+ 15:11 0:00 starman worker --port 8080 app.psgi --workers 3 noxx 4221 0.0 0.1 64460 12920 pts/3 S+ 15:11 0:00 starman worker --port 8080 app.psgi --workers 3 noxx 4222 0.0 0.1 64460 12756 pts/3 S+ 15:11 0:00 starman worker --port 8080 app.psgi --workers 3 noxx 4224 0.0 0.0 13580 936 pts/4 S+ 15:12 0:00 grep --colour=auto starman 


One master, three workers.

Deal with the order of execution. Now add another warning.

 warn 'IN BUILDER' 


The application is as follows:

 use strict; use warnings; use Plack; use Plack::Builder; use Plack::Request; warn 'AFTER IMPORT'; sub body { return 'body'; } sub body2 { return shift; } my $app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->body(body()); return $res->finalize(); }; my $main_app = builder { warn 'IN BUILDER'; mount "/" => builder { $app }; }; 


For one worker process, the output looks like this (startup command: starman --port 8080 --workers 1 app.psgi):

 2013/06/02-17:33:27 Starman::Server (type Net::Server::PreFork) starting! pid(4430) Resolved [*]:8080 to [::]:8080, IPv6 Not including resolved host [0.0.0.0] IPv4 because it will be handled by [::] IPv6 Binding to TCP port 8080 on host :: with IPv6 Setting gid to "1000 1000 4 24 27 30 46 107 125 1000 1001" AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 23. 


If we run the application with three workers, we will see the following picture in STDERR:

 AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 23. AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 23. AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 23. 


By making a request to localhost: 8080 /, you can easily make sure that nothing new in STDERR has appeared.

The following conclusions can be drawn:

This action will be performed at the start of the application. This is true both for the start of the script and for the builder section, if any.
This action will not be performed when querying the server.
Starman workflows start sequentially.
This makes it possible to construct heavy objects both at the start of the script and in the builder part.

Now add another warning to the code of the following form:

 warn 'REQUEST'; 


And we bring the application to the following form:

 use strict; use warnings; use Plack; use Plack::Builder; use Plack::Request; warn 'AFTER IMPORT'; sub body { return 'body'; } sub body2 { return shift; } my $app = sub { warn 'REQUEST'; my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->body(body()); return $res->finalize(); }; my $main_app = builder { warn 'IN BUILDER'; mount "/" => builder { $app }; }; 


Now run the application with one workflow (starman --port 8080 --workers 1 app.psgi). So far, nothing has changed:

 AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24. 


But it is worth making a request, as in STDERR a new entry will appear.

 REQUEST at /home/noxx/perl/psgi/app.psgi line 16. 


Summarize. With each request to the starman, only the code of the application itself will be executed (it is worth remembering return sub ...), but this code will not be executed at the start.

And now, let's say, one process fell. Add the following line to the return sub ...:

 die("DIED"); 


As a result, you should get the following application:

 use strict; use warnings; use Plack; use Plack::Builder; use Plack::Request; warn 'AFTER IMPORT'; sub body { return 'body'; } sub body2 { return shift; } my $app = sub { warn 'REQUEST'; my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->body(body()); die("DIED"); return $res->finalize(); }; my $main_app = builder { warn 'IN BUILDER'; mount "/" => builder { $app }; }; 

Run the application with one workflow, make a request. The application naturally falls. But the result is curious, although it is logical. The application did not fall, only two notifications appeared in STDERR:

 REQUEST at /home/noxx/perl/psgi/app.psgi line 16. DIED at /home/noxx/perl/psgi/app.psgi line 21. 


Now replace die ('DIED'); at exit 1; Run Starman, make a request to localhost: 8080 /. Now the workflow has fallen. This can be seen in STDERR, which will now look like this:

 REQUEST at /home/noxx/perl/psgi/app.psgi line 16, <$read> line 7. AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7, <$read> line 8. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 26, <$read> line 8. 


After each request, the workflow will fall, but the master process will raise it.

Let's leave Starman for a while. Let's try to run this application, for example, under Twiggy. If this server is not installed, then it's time to install it. The package is called Twiggy.

After installing Twiggy, launch our application with the following command:
 twiggy --port 8080 app.psgi 


And make a request. Everything is like Starman, except for one feature. The server has collapsed.

 noxx@noxx-inferno ~/perl/psgi $ twiggy --port 8080 app.psgi AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 26. REQUEST at /home/noxx/perl/psgi/app.psgi line 16, <> line 5. noxx@noxx-inferno ~/perl/psgi $ 


Of course, this is because Twiggy lacks a master process and there is no one to lift the fallen worker. And now it follows a very important point that must be taken into account. Before restarting the server, you must make sure that its code is correct and does not contain syntax errors. If you try to start an application that contains an error using Starman, several events will occur in the following order:



Execution errors are not so critical. Remove falls from the application, bringing it almost to the initial form:

 use strict; use warnings; use Plack; use Plack::Builder; use Plack::Request; warn 'AFTER IMPORT'; sub body { return 'body'; } sub body2 { return shift; } my $app = sub { warn 'REQUEST'; my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); $res->body(body()); return $res->finalize(); }; my $main_app = builder { warn 'IN BUILDER'; mount "/" => builder { $app }; }; 

And we will try to do the following exactly in this order:

Result:

 curl localhost:8080/ body 

Save the application, change the body function. Let now, for example, it returns nobody. We make a request - the result, if we did not restart the server, the following:
 curl localhost:8080/ body 

But it is necessary to make a restart, how things change:

 curl localhost:8080/ nobody 


Another important conclusion. In order for the application to be updated, changing the files is not enough. You must restart the server. Or send a special signal to the master process.

Starman and signals


Imagine that we have a large PSGI application, which cannot be stopped, because we have quite heavy libraries that are loaded into memory for, say, ten seconds.

Repeat the previous chain of actions, but with one change. Add sending signals.

The signal that tells Starman to reread is SIGHUP.

The command to send this signal looks like this:

 kill -s SIGHUP [pid] 

You can get the pid value with the following command:

 ps uax | grep starman | grep master 


Example command output:

 noxx 6214 0.8 0.1 54852 10288 pts/3 S+ 19:17 0:00 starman master --port 8080 --workers 1 app.psgi 

pid = 6214.

We check the request-response. Replace nobody back with body and run the application.

Result:
 curl localhost:8080 body kill -s SIGHUP 6214 curl localhost:8080 nobody 

In the meantime, in STDERR Starman we can see the following:

 AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24. REQUEST at /home/noxx/perl/psgi/app.psgi line 16. Sending children hup signal AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7, <$read> line 2. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24, <$read> line 2. REQUEST at /home/noxx/perl/psgi/app.psgi line 16, <$read> line 2. 

Thus, there are two ways to update a PSGI application. Which one to choose depends on the task.

Suppose you need another workflow. It can be added in two ways. Restart the server with the required parameter (--workers) or send a signal. The signal for adding one workflow is TTIN, for deletion - TTOU. If we want to stop the server completely safely, we can use the QUIT signal.

So. Run our application with one workflow:
 starman --port 8080 --workers 1 


Then add two processes by running the following command twice:
 kill -s TTIN 6214 


Starman process list:

 noxx 6214 0.0 0.1 54852 10304 pts/3 S+ 19:17 0:00 starman master --port 8080 --workers 1 app.psgi noxx 6221 0.0 0.1 64724 13188 pts/3 S+ 19:19 0:00 starman worker --port 8080 --workers 1 app.psgi noxx 6233 0.0 0.1 64476 12872 pts/3 S+ 19:26 0:00 starman worker --port 8080 --workers 1 app.psgi noxx 6239 2.0 0.1 64480 12872 pts/3 S+ 19:29 0:00 starman worker --port 8080 --workers 1 app.psgi 

In STDERR already familiar:

 AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7, <$read> line 4. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24, <$read> line 4. AFTER IMPORT at /home/noxx/perl/psgi/app.psgi line 7, <$read> line 4. IN BUILDER at /home/noxx/perl/psgi/app.psgi line 24, <$read> line 4. 


Then we remove one process:

 kill -s TTOU 6214 


We can see that the team had an effect by looking at the process list:
 noxx 6214 0.0 0.1 54852 10304 pts/3 S+ 19:17 0:00 starman master --port 8080 --workers 1 app.psgi noxx 6221 0.0 0.1 64724 13188 pts/3 S+ 19:19 0:00 starman worker --port 8080 --workers 1 app.psgi noxx 6233 0.0 0.1 64476 12872 pts/3 S+ 19:26 0:00 starman worker --port 8080 --workers 1 app.psgi noxx 6238 0.0 0.0 13584 936 pts/4 S+ 19:29 0:00 grep --colour=auto starman 

But in STDERR it will not be displayed.

And now we complete the work of our application by sending it a QUIT signal.

 kill -s QUIT 6214 

The server writes to STDERR:

 2013/06/02-19:32:15 Received QUIT. Running a graceful shutdown Sending children hup signal 2013/06/02-19:32:15 Worker processes cleaned up 2013/06/02-19:32:15 Server closing! 


And completes the job.

That's all you need to know about Starman in order to start working with it.

There is one more important detail. When you start Starman, you can specify the required module for loading through the master process using the -M key. But then the following restriction begins to work. Modules loaded via -M (-MDBI -MDBIx :: Class) will not be re-read with SIGHUP.

Another useful server option is -I. It allows you to specify the path to Perl modules before starting the master process. Starman also knows how to work with Unix sockets, but this opportunity will be discussed in more detail in future articles, starting with the article on deploying and administering Plack.

And finally, the -E flag, which sets the environment variable (PLACK_ENV) to the passed state.

The next article will be devoted to the asynchronous PSGI-server - Twiggy.

Dmitry Shamatrin

Source: https://habr.com/ru/post/248283/


All Articles