Feature Proposal: Foswiki.pm is to be split and session object is to be reconsidered.
Motivation
This subject is related to
NewOODesignPlan topic.
Description and Documentation
Foswiki.pm is overloaded. It's a mixture of static API functions and methods of the core of all Foswiki code – the
session
object. Actually it isn't really a session as some may expect. We need to change this.
Background
Aside of what is stated in the previous section I would like to mention another serious design flaw: total overuse of BEGIN blocks. Not only they're difficult to debug (Komodo, for example, cannot debug them whatsoever) but most likely they're not compatible with future PSGI and multi-domain hosting. Consider, for example,
%macros
hash which is global. If a plugin registers it's own macro then it becomes available across all handled domains independently of what plugins do they use.
The View
Foswiki.pm has to be split. I propose the original module to be a container for:
- StaticMethods aka API functions.
- System wide domain independent constants.
- Few execution environment adjustments.
The key words here are 'domain independent'. If something doesn't fit this demand – it doesn't fit into Foswiki.pm.
The code related to what we now call
session
I would extract and put into
Foswiki::App
class which would be the alpha and omega of processing a single request.
Why
App
? Because
Application
is too long!
Who needs another reason, really?
Ok, for those who actually does I would give the following reasons:
- It is puts together all other code (frameworks, modules, contribs, and plugins) as an application does.
- It gets through basic stages of an application life cycle: prepare, init, run, shutdown.
- It actually handles the task of organizing the interaction with a user by processing his requests.
Eventually the startup code of a CGI script shall consist of few lines:
#!env perl
use Foswiki::App;
Foswiki::App->run;
It is then the job of
Foswiki::App
constructor to do the rest:
- Determine the environment we're running in.
- Setup basic error handling.
- Read the configuration.
- Initialize the base structures and objects.
- Initiate the request handling.
- Collect the garbage, clean up, turn off the iron, the kitchen stove... Oops, it's from another topic.
And no BEGINS unless there is no other way.
use locale
doesn't fit this model because it depends on
%Foswiki::cfg
. But:
- It was told that the
locale
pragma is no good and different solution must to be used.
- For the time being until
locale
could be wiped out it's use might be regulated by some kind of environment variable. Apache can set shell environment on per-virtual host basis, for example. But then again: if I do understand the problem here any persistent environment similar to mod_perl or FastCGI would have the same problem of sharing the locale from the first time a module was loaded.
So,
Carthage locale
has to be removed!
Another big change with impact on plugins:
$Foswiki::Plugins::SESSION
will disappear. Plugin handlers without supplied
$topicObject
(aka
$meta
) parameter would need to get an additional one – like the
preload()
or tag handlers.
The above condition could be ignored if there is 100% guarantee that a process (I – system process identified by it's PID) serves no more than one request at a time. The latter means that no frameworks like POE or alike are used nor threads are involved. Though in latter case a variable could be declared thread-local. In POE case replacing variable's content upon context switching would resolve the problem too.
Unfortunately I'm running out of my time and have to stop here. But hopefully the seed has been thrown and the idea is clear.
New classes emerging from Foswiki.pm
In this section I will be introducing new classes brought to life in the process of splitting the Foswiki.pm. Due to lack of time and because the new classes might be a kind of a moving target detailed description shall not be expected within this proposal. Yet, most of their functionality including method names will be coming from Foswiki.pm and as such should be familiar to anybody interested.
Foswiki::Macros
Macros handling and expansion.
Foswiki::UI
Converted to a class. Request handling functions are to be moved into
Foswiki::App
. Would be a great place for UI-related staff like generic page generation in replace of CGI-based generation.
I'm also considering to move into
Foswiki::UI
pure UI-related code like:
- user handling
- methods like
getSkin()
or inlineAlert()
Changes in behavior
Some classes would do things differently or their role is to be changed.
Foswiki::Engine
Engine was previously the initiator of the request processing. This role is handed over to
Foswiki::App
. Engine used to setup
Foswiki::Request
– this is where
Foswiki::Request
would do the job better. Engine is to become a mere mediator between Foswiki core and the environment we're being ran under. In other words it will serve as a source of information (what connection is being used; what query parameters are supplied; what cookies are being sent) and as the output destination (i.e. – method
write()
).
Foswiki::Request
This class would take more active role in setting up itself – from parsing the path info string to fetching and setting various parameters like connection attributes (secure/insecure, port number), action name, etc.
Examples
$this->app->macro('SEARCH');
# Let's assume that %macros converted into a Foswiki::Macros object.
$this->app->macros->expand($topic); # $topic->expandMacros is still a comfortable wrapper.
# But if the above is considered an overkill:
$this->app->expandMacros($topic); # We can still have this form. Or preserve it as the only one.
# Passing additional app parameter to a newly created object every time might be pretty boring. Why not to automate it?
$this->create(
'Foswiki::Meta',
web => $this->web,
);
# create() method would basically be like this:
sub create {
my $this = shift;
my $class = shift;
return $class->new(
app => $this->app,
@_
);
}
# In certain cases data structures would require some kind of chaining (parent/child, prev/next).
# It won't burden the code too:
sub createChild {
my $orig = shift;
my $this = shift;
my $class = shift;
my $newObj = $orig->(
$this, $class,
parent => $this,
@_
);
return $this->addChild( $newObj );
}
$this->createChild( 'Foswiki::Meta', web => $subWeb );
Etc., etc., etc...
Impact
Implementation
--
Contributors: VadimBelman - 04 Mar 2016
Discussion
It is safe to assume one PID processes only one request at a time. So
Foswiki::Plugins::SESSION
could stay when there is no other reason to motivate its removal.
--
MichaelDaum - 04 Mar 2016
Take the following "Hello world" PSGI application. (install
cpanm Task::Plack
)
use Plack::Builder;
my $app = sub {
my $env = shift;
return [
200,
[
'Content-Type' => 'text/plain'
],
[
"Hello World from $env->{HTTP_HOST} PID: $$",
]
];
};
builder {
mount 'http://mylocal1/' => $app,
mount 'http://mylocal2/' => $app,
mount 'http://mylocal3/' => $app,
mount '/' => $app,
}
As you can see, the same application is mounted for
4 different hostnames.
For the testing add too your
/etc/hosts
:
127.0.0.1 localhost, mylocal1, mylocal2, mylocal3
Now enter the simple
starman
, and try on the browser
http://mylocal1:5000/
and in the another browser
http://mylocal2:5000/
. Reload few times the pages in both browsers - you will see different PIDs.
Now, stop the
starman
and run the
twiggy
command and reload few times both browsers. You will see
always the
same PID for both.
This is because different http servers are used. The starman is an preforked one (aka many workers), the twiggy is AnyEvent based (one "worker"). Even the basic "development" server (the simple
plackup
command) will show only one PID.
E.g. no, it isnt' safe assume that:
"one PID processes only one request at a time" , or maybe i didn't understand right what do you mean...
--
JozefMojzis - 04 Mar 2016
The question is whether
ALL of Foswiki is reentrant or not. From what I know all of these calling semantics run one thread at a time, even for event-based execution. At least that's what I'd assume. Otherwise we'd probably stuffed all along, not only with
Foswiki::Plugins::SESSION
.
--
MichaelDaum - 04 Mar 2016
Once in the memory, or not once in the memory, That is the question. Not exactly Hamlet, but ...
Imagine a scenario:
- want setup Foswiki for 500 different "local gardeners groups".
- e.g. such wikis are low traffic, but every group want have his own wiki, because they're otherwise "enemies"...
- so, each group will register his own domain.
- with traditional way, (aka apache name-based virtual-hosts + FCGI) we will have 500 copies of the Foswiki and 500 copies of the perl interpreter in the memory - isn't very effective.
So, would be nice to have an ability to run foswiki as
my $foswiki = sub { ... };
builder {
mount "$_" => $foswiki for (@all_500_domains);
}
aka - having only as many perl interpreters in the memory as much "workers" are executed under the
starman. (usually 10-20 processes). E.g. would be possible to run dozens of different Foswikis for many different hosts with only few real workers - e.g. call this as an: "memory-effeicient wiki-farming".
I don't know how much work is needed to achieve this. Maybe it will be "too much work". Need decide.
--
JozefMojzis - 04 Mar 2016
This is already doable using
VirtualHostingContrib and works fine. I think this is hunting ghosts unless you can prove that there actually is a problem. I doubt that as far as I can see.
--
MichaelDaum - 04 Mar 2016
I just
- showed an example of the PID thing - and AFAIK, the AnyEvent based
twiggy
server didn't uses threads. It is an simple select based event-loop. So, the PID
is always the same.
- and added and wish/question about: "how many times is the perl loaded into the memory for multiple virtual hosts"...
That's all. If it is already done, Great! But.. we still don't have an PSGI based Foswiki - so... i doubt about the "done".
--
JozefMojzis - 04 Mar 2016
Reference locales see:
CleanUpFoswikiLocales
--
JulianLevens - 05 Mar 2016 - 08:51
Just to make it 100% clear:
- each request owns the perl interpreter exclusively.
- we do not use concurrent threads within the same perl process, not for one request, not for multiple independent requests being served at the same time
- two requests hitting foswiki at the very same second will be handled by two independent processes
- each processes finishes from start to end, i.e it initializes all global variables within Foswiki ... including
Foswiki::Plugins::SESSION
- there is no such thing like not initializing
Foswiki::Plugins::SESSION
yet still serving a Foswiki request
- of course the same process will be free to handle the next request ... but only AFTER the previous one has been finished completely ... which includes cleaning up
Foswiki::Plugins::SESSION
and all objects that hang from it.
- even when a Foswiki::App is called within an event loop will that loop occupy the perl process exclusively
- only when the Foswiki::App is coming back from an event loop will it be ready to serve the next request
- we only have to make sure that each event loop leaves behind perl in a sane state, i.e. clean up global vars, destroy objects, etc.
That's why you will see the same
PID being used for multiple requests ... but only one after the other, never concurrent.
This all means that your server will only be able to handle as many concurrent requests at the same time as there are perl processes loaded into memory.
Anything else is queued up.
A sensible number of Foswiki workers is rather depending on the number of CPUs in your sever instead of the number of domains hosted on it.
--
MichaelDaum - 05 Mar 2016
Just to make it 100% clear for event-loop based servers too :).
Michael, You're precise and 100% right until we talking about the
traditional, old-school (in the early '90s - apache-like prefork) model.
With this, the only way scaling web-servers for handling more
requesrs and/or serving long duration requests is using the
"one request = one process (or thread) method".
As requests comes, they get assigned to one of the already preforke process,
which does nothing else other than handle that request from start to finish.
Imagine some long duration requests
(for example streaming multipart XMLHTTPRequest or similar long polling comet).
The response isn't finishes for long time. Using the old-school (aka blocking) logic this is solvable only by
increasing the process (threads) counts, otherwise the server
would become unresponsive because the
(as you said)
"..but only one after the other, never concurrent."
Because forking processes (or creating threads) are expensive,
and the processes (threads) many times does nothing just waiting
(but occupying memory), many modern web server uses another method:
single process (single thread) event-loop based servers.
The single process event-loop based servers uses
non-blocking IO,
which means: one process could serve dozens of
concurrent processes
Thats mean:
one process = many requests concurently.
For such web servers/apps
*doesn't true* the following:
- each request owns the perl interpreter exclusively
- two requests hitting the web-app at the very same second will be handled by two independent processes
Theyre called as "event loop" because the IO is non-blocking
and the server just loops over the created sockets and checks their state
(aka waits for events) a'la: connected, able to read, able to send, finished etc).
Everything is done in the same process - concurently - for many (partially) served requests.
The main point of the event-loop based server is the
concurency per requests. Therefore the
we only have to make sure that each event loop leaves behind perl in a sane state, i.e. clean up global vars, destroy objects, etc. isn't true also... Please google for non-blocking web servers.
Of course, the Foswiki team could decide: we will never support modern non-blocking web-servers, so we will not bothering with them. In such case - just ignore all the above. So long, and thanks for all the bits..
--
JozefMojzis - 07 Mar 2016
Did you check
psgi.multithread
and
psgi.multiprocess
in your psgi app's environment?
--
MichaelDaum - 07 Mar 2016
The relevant variables are server dependent and are set as:
e.g. they're correctly set for the event-loop based
twiggy
.
--
JozefMojzis - 07 Mar 2016
Means only
Corona is a problem using co-routines, but not PSGI/Plack in general. Is that so? Most seem to be using a kind of worker pool, such as
Starman.
As such I'd consider them in the same class as FCGI.
--
MichaelDaum - 07 Mar 2016
Damn RSS didn't dispay changes in the topic to me. Therefore I'm late to jump on the discussion train.
I think it takes to much to discuss the global $SESSION variable. We can keep it as Michael is totally right: as soon as it is one request per time per process – it's nothing to worry about. Even if someday we manage to develop event-driven Foswiki keeping $SESSION actual is only a matter of setting it to proper value on each context switch. Nothing to speak about.
I don't think we ever go in multithreading direction as perl5 and multithreading are nearly mutually exclusive things. Yet even if this happens some day $SESSION could be declared thread-local variable which is good enough too to me.
So, I think it shall stop now. What I'm mostly looking for here is for objections or proposals to the model itself. Especially I would like to focus on the OO API where ->app or $SESSION becomes the root of it all. I.e., using an example from the initial
ImproveOOModel topic,
getUrlHost()
would be replaced with
$app->req->urlHost
or by using Moo's delegations we may have a shortcut
$app->getUrlHost
.
--
VadimBelman - 07 Mar 2016
So, the decision:
- We will NOT support event-loop based PSGI web-servers, like CPAN:Twiggy .
- We will support only PSGI web-servers, which traditional one request == one process model, like
starman
.
Not really understand the reason of the decision - but OK. Because i'm really very limited by my english - i'm unable explain things to be understandable - even the simple ones. The lack of the english knowledge is my fault - so it is pointless to blabbing more here.
I will be happy with any Foswiki - even the current one is great...
--
JozefMojzis - 07 Mar 2016
Jozef, you got it wrong. What I'm saying is that if we speak about non-threaded environments then whichever framework is responsible for request handling there will be no more than one request served per process at any given moment of time. This is true even in event-driven environments simply by design. It means that there is always a solution to have
$SESSION
initialized correctly.
For threading the situation would be different but solvable too.
So, nobody is imposing any limits. We would only need different solutions for different environments. Which means in turn that we can happily let the variable be there where it is now and have the same functionality.
--
VadimBelman - 07 Mar 2016
I have committed first outline for the
Foswiki::App
class. It's just a few cell embryo (or a pen esquisse – whatever one prefers) of the future core object. Yet it would trigger some discussion even now.
An important note: it's gonna be much less compatible with the current design as expected. Otherwise it wouldn't worth the efforts.
--
VadimBelman - 17 Mar 2016
Abandoned
--
MichaelDaum - 14 Oct 2024