Mots-clé : apr apache portable runtime

Librairie Apr : tutoriels 3 et 4

Pour information : regardez le tutoriel au complet, en Anglais, ici.
Vous trouverez tout ce tutoriel séparé en plusieurs pages ici.

3. memory pool (apr_pool_t)

La plupart des fonctions de libapr sont dépendants de pools mémoire. Grâce aux pools mémoire, vous pouvez facilement gérer des groupes de portions de mémoire.

Imaginez un cas sans le système de pool mémoire, où vous devez allouer chaque portion de mémoire. Vous devez libérer chacun d’eux. Si vous avez fait dix allocations, vous devez faire dix libérations, sinon vous allez avoir des fuites mémoire. Le principe de pool mémoire résout ce genre de problème. Après avoir crée un pool mémoire, vous pouvez allouer autant de morceaux de mémoire que vous voulez sur ce pool. Pour tout libérer, la seule chose que vous ayez à faire est de détruire le pool mémoire. Le pool s’occupe de libérer le reste.

Il y a deux gros avantages :

  1. Comme expliqué juste avant, cela évite les fuites mémoire ;
  2. Ensuite, le coût machine d’allocation est réduit.

D’une certaine façon, vous devez tout de même programmer comme si vous étiez dans une session. Un pool mémoire est un peu comme un contexte de session, c’est à dire que tous les objets qui y sont liés ont la même durée de vie. Vous pouvez contrôler un groupe d’objets dans un contexte de session. Au début d’une session, vous créez un pool mémoire. Ensuite, vous créez des objets sur ce pool mémoire pendant cette session. Notez que vous n’avez pas à vous préoccuper de leur durée de vie. A la fin de la session, la seule chose que vous ayez à faire est de libérer le pool mémoire.

REMARQUE : En général, le contrôle de la durée de vie des objets est la chose la plus difficile en programmation. C’est pour cela qu’il y a plein d’autres techniques pour cela, telles que les « pointeurs intelligents, le ramasse-miettes (GC – garbage collector) etc. Il est difficile d’utiliser de telles techniques en même temps. Comme le pool mémoire est l’une de ces techniques, il vous faire attention aux mélanges !

Il y a trois fonctions principales de l’API, concernant les pools mémoire :

/* extrait de apr_pools.h */
APR_DECLARE(apr_status_t) apr_pool_create(apr_pool_t **newpool, apr_pool_t *parent);
APR_DECLARE(void *) apr_palloc(apr_pool_t *p, apr_size_t size);
APR_DECLARE(void) apr_pool_destroy(apr_pool_t *p);

On crée un pool mémoire via apr_pool_create(). Ce pool existe tant qu’on ne l’a pas libéré via apr_pool_destroy(). Le premier argument de apr_pool_create() est un argument-resultat. Un nouvel objet « pool mémoire », apr_pool_t, est renvoyé par cet appel à l’API. On appelle apr_palloc() pour allouer une portion de mémoire en précisant la taille. Jetez un coup d’oeil à mp-sample.c pour voir un exemple simple.

/* extrait de mp-sample.c */
apr_pool_t *mp;
/* créer un pool mémoire. */
apr_pool_create(&mp, NULL);
/* allouer une portion mémoire sur le pool */
char *buf1;
buf1 = apr_palloc(mp, MEM_ALLOC_SIZE);

En résumé, on peut utiliser apr_palloc() comme malloc(3). On peut aussi appeler apr_pcalloc(). Comme vous l’imaginez, apr_pcalloc() ressemble fortement à calloc(3). apr_pcalloc() renvoie une portion mémoire intégralement initialisée avec des zéro. Si vous vous servez de malloc(3)/calloc(3), vous aurez besoin d’appeler free(3) pour chaque portion allouée. A l’inverse, vous n’avez pas besoin de faire ça pour chaque portion allouée sur un pool mémoire. Vous appelez simplement apr_pool_destroy() avec le pool concerné et tout ce qui a été alloué est libéré.

REMARQUE : Il n’y a pas de limitation sur la taille que vous pouvez allouer avec apr_palloc(). Néanmoins, ce n’est pas une bonne idée d’allouer de gros morceaux de mémoire avec une gestion de mémoire orienté « pool ». C’est simplement parce que la gestion via un pool mémoire a été conçue pour gérer efficacement des petites allocations. En réalité, la taille initiale d’un pool mémoire est de de 8 kb. Si vous avez besoin d’allouer quelque chose de plus large, p.ex. plusieurs méga bytes, vous devriez éviter la gestion via un pool mémoire.

REMARQUE : Par défaut, un manager de pool mémoire ne libère jamais la mémoire allouée au système tant qu’il n’est pas détruit. Si un programme tourne pendant longtemps, il aura des problèmes. Je vous conseille de préciser dès le début une taille limite maximale comme suit :

/* exemple qui applique une limite haute pour forcer le
 * manager du pool à libérer de la mémoire au système
 * à partir d'un certain seuil
 */
#define YOUR_POOL_MAX_FREE_SIZE 32 /* taille max. du pool */
apr_pool_t *mp;
apr_pool_create(&mp, NULL);
apr_allocator_t* pa = apr_pool_allocator_get(mp);
if (pa) {
    apr_allocator_max_free_set(pa, YOUR_POOL_MAX_FREE_SIZE);
}

Il y a deux autres fonctions de l’API que vous devez connaitre. L’une d’elles est apr_pool_clear(), et l’autre est apr_pool_cleanup_register(). apr_pool_clear() ressemble à apr_pool_destroy(), mais le pool mémoire est toujours réutilisable. Le code typique est le suivant :

/* exemple montrant comment fonctionne apr_pool_clear() */
apr_pool_t *mp;
apr_pool_create(&mp, NULL);
for (i = 0; i < n; ++i) {
    do_operation(..., mp);
    apr_pool_clear(mp);
}
apr_pool_destroy(mp);

Le pool mémoire est utilisé dans do_operation(), qui, imaginons, alloue plein de morceaux mémoire. Si vous n’avez pas besoin de tous les morceaux alloués hors de do_operation(), vous pouvez appeler apr_pool_clear(). Ainsi vous limiterez l’utilisation mémoire. Si vous connaissez bien le principe du fonctionnement du système de la pile locale, vous pouvez imaginer que la gestion de pool mémoire est une « pile mémoire locale ». L’appel de apr_palloc() est similaire au fait de déplacer SP(stack pointer), et appeler apr_pool_clear() est similaire à faire reculer SP. Les deux opérations sont très légères en termes de calcul CPU.
La fonction apr_pool_cleanup_register() offre la possibilité d’appeler automatiquement des fonctions de libération lorsque le pool est nettoyé via apr_pool_clear() ou détruit vie apr_pool_destroy(). Dans les fonctions de callback, vous pouvez implémenter un code de libération de vos objets, ou de finalisation, liés au nettoyage/destruction du pool mémoire.
La dernière chose concernant les pools mémoire est le sous-pool. Chaque pool a la possibilité d’avoir un parent. Pour être plus précise, les pools mémoire sont organisés dans un arbre. Le deuxième argument de  apr_pool_create() est le parent du pool mémoire à créer. Si vous passez NULL en tant que parent, le nouveau pool est positionné à la racine de l’arbre. Vous pouvez créer des « sous-pools » mémoire en les liant à ce dernier. Quand vous appelez apr_pool_destroy() pour un pool mémoire dans l’arbre, tous ses descendants sont aussi détruits. Quand vous appelez apr_pool_clear(), le pool mémoire reste en vie, il est simplement nettoyé, mais les enfants sont détruits. Quand un enfant est détruit, les fonctions callback liées au nettoyage/destruction dont on a parlé précédemment sont appelées.

REMARQUE : Le bogue suivant est très courant : vous passez NULL comme paramètre pool à la fonction callback de nettoyage. Il faut plutôt utiliser apr_pool_cleanup_null, comme le montre l’exemple :

/* pseudo code sur le bogue habituel de pool mémoire */
/* apr_pool_cleanup_register(mp, CONTEXTE_DE_VOTRE_CODE, CALLBACK_DE_VOTRE_CODE, NULL); C'EST UN BOGUE */
/* Version corrigée : */
apr_pool_cleanup_register(mp, CONTEXTE_DE_VOTRE_CODE, CALLBACK_DE_VOTRE_CODE, apr_pool_cleanup_null);

4. Error status (apr_status_t)

La plupart des fonctions de la librairie libapr renvoient une valeur de type apr_status_t. La valeur apr_status_t est très souvent, soit APR_SUCCESS soit autre chose. APR_SUCCESS indique que tout s’est correctement déroulé. Le code typique ressemble à cela :

/* pseudo code sur la vérification du retour apr_status_t */
apr_status_t rv;
rv = apr_pool_create(&mp, NULL);
if (rv != APR_SUCCESS) {
    /* Gestion de l'erreur */;
}

libapr définit quelques statuts d’erreur tels que APR_EINVAL, et des macros pour vérifier des types d’erreurs telles que APR_STATUS_IS_ENOMEM((). Quelques unes d’entre elles sont vraiment très utiles, surtout pour la gestion des problèmes de portabilité. Un exemple typique est la macro APR_STATUS_IS_EAGAIN(). Historiquement, il y a deux erreurs qui ont le même chiffre mais pas la même signification, EAGAIN et EWOULDBLOCK. La macro APR_STATUS_IS_EAGAIN() gère ce problème.
Néanmoins, c’est presque impossible de gérer toutes les erreurs sans prendre en compte le système sous-jacent. La librairie libapr ne réinvente pas la roue. Ce qu’elle fait est très simple :

  • En cas de succès, retour = APR_SUCCESS
  • En cas d’erreur concernant uniquement la librarie elle-même, retour = APR_XXX
  • En cas d’erreur système commune à tous les systèmes d’exploitation, retour = APR_XXX
  • En cas d’erreur spécifique à un système d’exploitation, retour = numéro d’erreur du système plus un offset

Je vous conseille de suivre ces règles simple :

  1. Comparer la valeur de retour avec APR_SUCCESS
  2. Si vous avez besoin de plus de détails sur l’erreur, comparer avec d’autres valeurs d’erreur définies dans la librairie

Un fonction extrêmement utile est apr_strerror(). Vous pouvez afficher la chaine décrivant l’erreur de cette façon :

/* pseudo code expliquant apr_strerror() */
apr_status_t rv;
rv = apr_xxx_xxx();
if (rv != APR_SUCCESS) {
    char errbuf[256];
    apr_strerror(rv, buf, sizeof(buf));
    /* afficher la description de l'erreur */
    puts(errbuf);
}

Librairie Apr : tutoriel : aval officiel de l'auteur

J’ai eu l’aval officiel de l’auteur pour tout traduire et mettre en ligne sur mon site :
Voilà ce que j’ai demandé :

You’ve done a really really really good work with your apr tutorials.
I’d like to help the community by translating it in French and making it available in my site.
Example : (Exemple de document traduit)
May I go on or should I stop ?

Voilà sa réponse reçue au moment même où je fais cet article :

Hi, Olivier Pons,
Surely, you can go on.
It is my pleasure.
When you let me know the exact (I mean, a kind of permanent) URL, I will put the link on my site.
Thank you very much.
– INOUE Seiichiro

Pour information : regardez le tutoriel au complet, en Anglais, ici.
Vous trouverez tout ce tutoriel séparé en plusieurs pages ici.

Librairie Apr : tutoriel : mp-sample.c

Note

Regardez le tutoriel au complet, en Anglais, ici.
Vous trouverez tout ce tutoriel séparé en plusieurs pages ici.
Ce fichier est l’exemple le plus simple pour mettre en oeuvre les fonctions apr_xx
Il vient d’ici.

/**
 * apr tutorial sample code
 * http://dev.ariel-networks.com/apr/
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <apr_general.h>
#include <apr_pools.h>

#define MEM_ALLOC_SIZE		1024

/**
 * exemple de pool mémoire
 * @remark Vérif. des erreurs omise
 */
int main(int argc, const char *argv[])
{
    apr_status_t rv;
    apr_pool_t *mp;
    char *buf1;
    char *buf2;

    /* initialisation par processus (per-process) */
    rv = apr_initialize();
    if (rv != APR_SUCCESS) {
        assert(0);
        return -1;
    }

    /* créer un pool mémoire. */
    apr_pool_create(&mp, NULL);

    /* allouer de la mémoire via le pool */
    buf1 = apr_palloc(mp, MEM_ALLOC_SIZE);
    buf2 = apr_palloc(mp, MEM_ALLOC_SIZE);

    /* détruire le pool mémoire
      (ce qui était alloué au dessus sera
      automatiquement libéré) */
    apr_pool_destroy(mp);

    apr_terminate();
    return 0;
}

Librairie Apr : tutoriels 1 et 2

1. Disponibilité du tutoriel au complet

Regardez le tutoriel au complet, en Anglais, ici.
Vous trouverez tout ce tutoriel séparé en plusieurs pages ici.

2. Un squelette de code pour libapr

Je pense que c’est une bonne idée d’écrire un « squelette de code » au début, lorsque vous commencez à apprendre une nouvelle librairie ou un nouveau framework. Un ‘squelette de code’ est le plus petit morceau de code possible que vous pouvez compiler et exécuter (malgré le fait qu’il ne fasse rien de vraiment utile).
Heureusement, le squelette de code que l’on va faire pour la libapr est beaucoup plus simple que la plupart des frameworks modernes. Voyons apr-skeleton.c. On appelle apr_initialize() pour ouvrir la librairie, et apr_terminate() pour la fermer. C’est tout. Comme vous pouvez l’imaginer, le code ne fait rien du tout.
libapr n’est pas une librairie de type framework. Pour être plus précis, libapr ne vous aide pas du tout à concevoir la structure de votre code programme. Il y a des avantages et des inconvénients. L’avantage le plus important est que l’utilisation de libapr est très facile à utiliser et à intégrer dans du code existant. L’inconvénient principal est que vous devez concevoir la structure complète du code lorsque vous utilisez libapr.
Ici, nous avons tout de même quelques règles et style concernant libapr :

  • la règle de nommage est très simple et claire ;
  • les types de données opaques sont souvent utilisés (p.ex. les types incomplets) ;
  • la plupart des retours de fonctions sont apr_status_t et si besoin est, certaines variables en paramètre sont aussi des arguments résultats ;
  • règles de pool mémoire.

On peut voir les styles et les règles directement par cet exemple :

/* extrait de mp-sample.c */
apr_status_t rv;
apr_pool_t *mp;
rv = apr_pool_create(&mp, NULL);

Voici l’explication du code : ici ne regardez que la notation. Vous voyez le préfixe apr_. Ce dernier indique que le symbole fait partie de l’espace de nommage de la librairie libapr. Le suffixe _t précise que le nom concerné est un type.
apr_pool_t est un type opaque. Cela signifie que la structure de ce type n’est pas publique. En utilisant une terminologie de style OO (Object Oriented), toutes les variables membres sont privées. Vous ne pouvez pas y accéder directement. Vous ne pouvez pas les voir dans les fichiers « .h » non plus. La seule chose que vous puissiez faire pour ce type est d’appeler une API qui l’utilise, telle que apr_foo_bar() par exemple. Plus important encore, vous ne pouvez créer une instance de ce type dynamiquement. Vous êtes obligé de passer par une fonction d’allocation de la librairie. Seule libapr sait comment construire et détruire ce genre d’objets.
Comme vous pouvez le voir, le type de retour de la fonction apr_pool_create() est apr_status_t. apr_status_t est soit un code indiquant un status soir un code d’erreur. apr_status_t is est utilisé couramment en tant que type de valeur de retour par la plupart des fonctions de l’APIs. De la même façon, on peut recevoir des résultats de fonction via des arguments. De tels arguments sont appelés arguments-résultat. Il y a beaucoup d’arguments-résultat dans le monde de la libapr.
En général, si vous voyez le type apr_foo_t, vous verrez des fonctions apr_foo_bar(), qui ont un lien avec le type apr_foo_t. Le code qui suit est un pseudo code courant :

/* pseudo code of libapr. Vérif. des erreur omise */

apr_status_t rv;
apr_foo_t *foo;

/* créer un objet @foo selon @args : */
rv = apr_foo_create(&foo, args...);

/* faire quelque chose avec @foo : */
rv = apr_foo_do_something(foo, args...); /*  */

/* détruire l'objet @foo. */
apr_foo_destroy(foo);

/* NB : Parfois la destruction d'un objet est
   faite implicitement lorsque le pool mémoire
   associé est détruit. */