[spip-dev] calculs en background

Coucou,

est-ce que quelqu'un sait comment faire (en php) des calculs en background ?
Mon idée était de faire comme cela : au lieu de faire les calculs lourds sur
des pages prises au hasard (et d'avoir donc des pages qui mettent, de temps
à autre, des plombes à s'afficher), les faire "derrière une image", en
envoyant l'image d'abord, puis en effectuant les calculs.

Mais ! Le problème est que tant que le script n'a pas fini de calculer, le
serveur ne flushe pas l'image, et la connexion reste active. C'est un peu
dommage. J'ai regardé du côté de
http://fr3.php.net/manual/en/function.pcntl-fork.php
mais c'est une fonction vraiment pas standard.

-- Fil

Hello,

est-ce que quelqu'un sait comment faire (en php) des calculs en
background ?

Tu devrais pouvoir trouver ton bonheur avec
register_shutdown_function() :

"With a shutdown function, when the remote user hits his STOP button,
the next time your script tries to output something PHP will detect
that the connection has been aborted and the shutdown function is
called. This shutdown function will also get called at the end of your
script terminating normally, so to do something different in case of a
client disconnect you can use the connection_aborted() function. This
function will return TRUE if the connection was aborted."

-Nicolas

est-ce que quelqu'un sait comment faire (en php) des calculs en background ?
Mon idée était de faire comme cela : au lieu de faire les calculs lourds sur
des pages prises au hasard (et d'avoir donc des pages qui mettent, de temps
à autre, des plombes à s'afficher), les faire "derrière une image", en
envoyant l'image d'abord, puis en effectuant les calculs.

Mais ! Le problème est que tant que le script n'a pas fini de calculer, le
serveur ne flushe pas l'image, et la connexion reste active. C'est un peu
dommage.
J'ai regardé du côté de
http://fr3.php.net/manual/en/function.pcntl-fork.php
mais c'est une fonction vraiment pas standard.

Ce n'est pas dommage, c'est normal dans le contexte d'exécution de PHP.
Le but est que PHP soit "sûr" en environnement mutualisé, donc justement
d'interdire de lancer des calculs à qui mieux mieux.

Les fonctions du genre "fork" sont normalement désactivées en safe mode.

a+

Antoine.

Ca pourrait marcher avec une iframe invisible dans les pages du site public (genre là où on balance déjà les boutons de correspondance des admins), iframe qui appelle la page de calcul qui va bien. Et aussi iframe dans l'espace privé.

A*

Ca pourrait marcher avec une iframe invisible dans les pages du site
public (genre là où on balance déjà les boutons de correspondance des
admins), iframe qui appelle la page de calcul qui va bien. Et aussi iframe
dans l'espace privé.

Une iframe, bof, une image (invisible, genre spacer.gif) c'est aussi bien,
et plus courant.

-- Fil

> Et aussi iframe dans l'espace privé.

En particulier l'optimisation qui se déroule quand tu te connectes dans
ecrire/ pour la première fois depuis 24 h est un peu chiante ; je l'ai un
peu bidouillée avec bonjour=oui, mais c'es pas ça :slight_smile:

-- Fil

> Ca pourrait marcher avec une iframe invisible dans les
pages du site
> public (genre là où on balance déjà les boutons de
correspondance des
> admins), iframe qui appelle la page de calcul qui va bien.
Et aussi iframe
> dans l'espace privé.

Une iframe, bof, une image (invisible, genre spacer.gif)
c'est aussi bien,
et plus courant.

-- Fil

C'est pas con mais ça ne résoud pas le problème : le mec qui a un JS qui se déclenche onload (qu'on aime ou qu'on n'aime pas), son JS ne se déclenchera que quand la page et *tous* ses composants seront chargés.

Donc ce sera potentiellement du même ordre de ralentissement. (idem sans JS, le temps de chargement total de la page sera ralenti de toute façon)

Comme le disait Antoine, c'est normal dans le contexte d'exécution de PHP, qui se limite à un comportement de script de serveur web.

Je repense à un mec qu'on avait vu assis par terre dans le Forum Social, qui disait qu'il s'était mis un petit script sur le serveur pour certaines tâches de fond.

On pourrait tenter de lancer directement un script shell "détaché" de PHP, pour voir ? Ah non, mauvaise idée, puisqu'on ne maîtrise pas l'environnement serveur (win vs linux vs mac, etc). A moins d'arriver à lancer un autre fichier PHP en mode ligne de commande, mais pour l'instant je n'ai que la théorie, je ne sais pas bien comment on pourrait faire...
(quelque chose comme shell_exec <http://fr.php.net/manual/en/function.shell-exec.php&gt; peut-être)

C'est pas con mais ça ne résoud pas le problème : le mec qui a un JS qui
se déclenche onload (qu'on aime ou qu'on n'aime pas), son JS ne se
déclenchera que quand la page et *tous* ses composants seront chargés.

Oui mais la meuf qui fait en javascript un image.preload(), ça ne ralentira
pas son affichage, puisque justement le preload n'affiche rien !

-- Fil

Moralité : du javascript au sexisme, il n'y a qu'un pas.

> C'est pas con mais ça ne résoud pas le problème : le mec
qui a un JS qui
> se déclenche onload (qu'on aime ou qu'on n'aime pas), son JS ne se
> déclenchera que quand la page et *tous* ses composants
seront chargés.

Oui mais la meuf qui fait en javascript un image.preload(),
ça ne ralentira
pas son affichage, puisque justement le preload n'affiche rien !

Même sans JS tu auras le temps de latence, puisque tu ne sauras pas quelle image se charge avant ton image "piégée", vu que HTTP ne permet pas de prioriser les requêtes.

Tu connais des meufs qui font du preload() alors ? :slight_smile:

Même sans JS tu auras le temps de latence, puisque tu ne sauras pas quelle
image se charge avant ton image "piégée", vu que HTTP ne permet pas de
prioriser les requêtes.

Non, car les images sont chargées en parallèle (le plus souvent). Oui car le
brouteur continuera à afficher un globe tournant... Mais register_truc() m'a
l'air plus prometteur. Il faudrait grouper les calculs en question dans un
inc_background.php3 ou inc_cron.php3, ça ferait une librarie sympa de plus
dans SPIP :slight_smile:

Tu connais des meufs...

Evite, svp.

-- Fil

Fil wrote:

Même sans JS tu auras le temps de latence, puisque tu ne sauras pas
quelle image se charge avant ton image "piégée", vu que HTTP ne
permet pas de prioriser les requêtes.

Non, car les images sont chargées en parallèle (le plus souvent). Oui
car le brouteur continuera à afficher un globe tournant... Mais
register_truc() m'a l'air plus prometteur. Il faudrait grouper les
calculs en question dans un inc_background.php3 ou inc_cron.php3, ça
ferait une librarie sympa de plus dans SPIP :slight_smile:

Pour ceux qui ne peuvent pas avoir accès à cron sur leur serveur, il y a
www.webcron.org

J'ai procédé à qq expérimentations à propos du calcul en background.
Il y a en fait 2 problèmes.

1. Spip ne profite pas pleinement du protocole http/1.1 en ce qu'il ne calcule pas, et donc
ne transmet pas, le Header "content-length"; du coup, le client est obligé d'attendre la fin
de l'exécution du processus serveur avant de conclure qu'il a tout reçu.

2. Certains clients ignorent ce header, et attendent donc la fin du processus serveur quand bien meme celui-ci leur a indiqué la taille de ce qu'il transmet et qu'il a fini de le transmettre.

On peut tester son client en mettant le fichier suivant dans le répertoire principal de Spip:

<?php
  Header("Content-Type: text/html;");
  $a= '<html><body>foo<img src="ecrire/img_pack/warning.gif"></body></html>';
  Header("Content-Length: " . strlen($a) .";");
  echo $a;
  flush();
  sleep(3);
?>

Résultats des courses sauf erreur de ma part:
  IE lance le GET IMG avant le sleep sour MacOSX
  Safari (MacOSX) lance le GET IMG après le sleep
  Mozilla lance le GET IMG après le sleep sous MacOSX
  Netscape lance le GET IMG avant le sleep sous Linux

Il faudrait tester IE sur la plate-forme dont le nom m'échappe,
mais le fait qu'il existe déjà 2 clients sur les 2 plates-formes testées
qui profitent du Content-length mmontre que Spip pourrait utilement calculer ce header.

Fil wrote:

Coucou,

est-ce que quelqu'un sait comment faire (en php) des calculs en background ? Mon idée était de faire comme cela : au lieu de faire les calculs lourds sur
des pages prises au hasard (et d'avoir donc des pages qui mettent, de temps
à autre, des plombes à s'afficher), les faire "derrière une image", en
envoyant l'image d'abord, puis en effectuant les calculs.

Mais ! Le problème est que tant que le script n'a pas fini de calculer, le
serveur ne flushe pas l'image, et la connexion reste active. C'est un peu
dommage. J'ai regardé du côté de http://fr3.php.net/manual/en/function.pcntl-fork.php
mais c'est une fonction vraiment pas standard.

et le fork, il a fortement tendance a bloquer le navigateur Web; de meme
que le fork n'est peut-etre pas dispo en environnement mutualisé;

il faut peut-etre regarder ces fonctions :

- PHP: socket_shutdown - Manual :

   fermeture de la socket ; ca permettrait de liberer la connexion TCP
   mais ca va en sens contraire du keep-alive, je me demande comment
   ca se comporte dans ce contexte.

- PHP: register_shutdown_function - Manual

   dans le forum, il est dit que c'est le seul moyen d'effectuer un traiment batch
   asynchrone.

   > This is important, because as far as I can tell, the documented functionality of
   > register_shutdown_function() is the *ONLY* way to achieve anything resembling asynchronous
   > processing with PHP in a web server environment. In my mind this is a severe limitation,
   > because if you have some processing intensive task that needs to be done and you want to
   > do it in PHP, there is no way to have it occur without an accompanying wait in a browser window.
   > Sure you can use ignore_user_abort() to ensure that processing doesn't get cancelled by an
   > impatient user (or impatient browser for that matter), but usability is still compromised.

- PHP: register_tick_function - Manual

   ca pourrait peut-etre servir qui sait ... c'est un top horloge, une sorte de iddle task

- PHP: ignore_user_abort - Manual

   sans doute a appeler en debut du traitement batch asynchrone.

j'en vois pas d'autre pour l'instant ; de meme je n'ai pas detecté sur google de page traitant
de facon exhaustive de ce probleme.

il est fort probable que ce probleme puisse est traité de 2 facon différente :
  - en intranet, il est assez facile de placer une crontab sous Linux, Unix voir sous Windows
    qui appelle un script cron.php
  - en Internet, le site web cron.xxx doit etre capable d'appeler ce meme script.
  - existe-t-il d'autres cas ?

pour le probleme du content size dans le header, il suffit de faire qq chose de ce genre je presume :

  ob_start()

  // blablabla
  // ou code SPIP

  ob_stop();
  $content = ob_getcontent();

  header("Content-type: html");
  header("Content-length: ", strlen($content));
  header("blabla ...");

  echo $content;

Fil wrote:

Le problème est que tant que le script n'a pas fini de calculer, le
serveur ne flushe pas l'image, et la connexion reste active.

il faut peut-etre regarder ces fonctions :

- PHP: socket_shutdown - Manual :

  fermeture de la socket ; ca permettrait de liberer la connexion TCP
  mais ca va en sens contraire du keep-alive, je me demande comment
  ca se comporte dans ce contexte.

Il n'y a pas de keep-alive dans le Spip actuel précisément parce qu'il reste en http/1.0.
Je pense que ta solution devrait marcher (mais je ne devrais pas le dire puisque je milite
pour une solution "par le haut" consistant à implémenter le 1.1 :-)).

pour le probleme du content size dans le header, il suffit de faire qq chose de ce genre je presume :

ob_start()

// blablabla
// ou code SPIP

ob_stop();
$content = ob_getcontent();

header("Content-type: html");
header("Content-length: ", strlen($content));
header("blabla ...");

echo $content;

Meme remarque: ca devrait marcher, mais je trouve que c'est une solution de fortune.
Il faut bien voir que l'existence de balises '<?php' fait que les caches ne sont pas
totalement des caches: dans le cas des forums, ce qui est caché représente 10% du html
finalement transmis. J'ai amélioré ce cas-là et l'accélaration de l'affichage est visible,
mais pas encore suffisamment réactif à mon gout.

      Emmanuel

J'ai essayé vite fait de regarder ce que ça donnait tout ça.
J'ai commancé par faire ça :

        <html>
        <body>
        AVANT : <?=date("s") ?><br>
        <?
          function plustard() {
            sleep(2);
            $f= fopen("testBg", "a");
            fputs($f, date("s"));
            fclose($f);
          }
          register_shutdown_function(plustard);
        ?>
        APRES : <?=date("s") ?><br>
        .
        </body>
        </html>

  si je fais un hit à 13h25m10s par exemple, la date affichée après les
mots AVANT et APRES est "10" et j'ai un "20" qui apparait dans mon fichier
mais la page s'affiche à 20.
  donc, ça marche impec sauf que le browser continue à poireauter.

  j'ai ensuite essayé avec ob_start/ob_get_length/ob_end_flush :
le comportement est exactement le même (ça attend) aussi bien avec
IE que mozilla, même en ajoutant un exit() en fin de script.

  bref, content-lenght ou pas, visuellement, le coup du
register_shutdown_function a l'air ok, mais l'internaute vois l'icone
de chargement continuer à tourner.
  ou alors j'ai loupé une marche ?

le code final :

        <? ob_start(); ?>
        <html>
        <body>
        AVANT : <?=date("s") ?><br>
        <?
          function plustard() {
            sleep(10);
            $f= fopen("/home/httpd/html/TESTS/testBg", "a");
            fputs($f, date("s")." : ".ob_get_length()."\n");
            fclose($f);
          }
          register_shutdown_function(plustard);
        ?>
        APRES : <?=date("s") ?><br>
        .
        </body>
        </html>
        <? $len=ob_get_length();
           header("Content-type: text/html");
           header("Content-length: ".$len);
           ob_end_flush();
           exit();
        ?>
        
À+, Pif.

J'ai essayé vite fait de regarder ce que ça donnait tout ça.

Merci ! Ca c'est constructif :slight_smile:

Bon, on n'a qu'à mettre ces calculs derrière une image et basta. Il faut
juste que l'image préempte les gros calculs des pages spip (car pour ne pas
obliger le webmestre à changer sa maquette ce doit être optionnel). Je vois
bien le scenario suivant :

spip_background.php3 :
    si flag 'background' :
        ecrire_meta(background image = heure courante)
    allume le flag 'background' (DEFINE ...)
    $fond='spacer.gif'; (attention là il y a un petit souci de format, on
        peut le régler sans doute en admettant que si $fond vaut '' on ne
        cherche pas de cache)
    appelle inc-public

inc-public, à chaque calcul un peu lourd :
        si (
            (lire_meta(background image) est vieux (1 heure, par exemple))
        ou defined('background')))
            faire le calcul

Au total on touche très peu à SPIP, mais l'image background si elle est
présente préempte ces calculs lourds. Et si on l'enlève bah les calculs se
font sur une page normale, après une heure d'hésitation.

-- Fil

    si flag 'background' :
        ecrire_meta(background image = heure courante)
    allume le flag 'background' (DEFINE ...)
    $fond='spacer.gif'; (attention là il y a un petit souci de format, on
        peut le régler sans doute en admettant que si $fond vaut '' on ne
        cherche pas de cache)
    appelle inc-public

Sur le fond, ça me paraît la solution la plus raisonnable si on veut
résoudre ce problème mais heu... attention quand même à ne pas doubler
la charge du serveur (deux inc-publics par page vue au lieu d'un seul
!). Il faut donc ruser.

Amicalement

Antoine.