lundi 15 novembre 2010

Things learned while writing an erlang driver

  I've spent a couple of days writing an erlang binding for zfec. The binding takes the form of a driver, that is, a shared library written in C that communicates with erlang according to the defined interface.

  The reference doc was ambiguous for some points, which gets summarized here:

Q: How is the Command parameter of open_port() passed to the ErlDriverEntry .start callback ?
A: The Command is a string, and the first word (whitespace-separated) must be the name of the driver. The whole string, including the driver name, is passed to the callback.

Q: If I send a list of binaries to the port, can I expect to get every one of them separately with the outputv callback ? How does the SysIOVec struct work ?
A: NO. Small binaries, and presumably sub binaries spliced from a common one, may get merged in the resulting ErlIOVec. I've written this to iterate over an ErlIOVec. The doc defines:

typedef struct {
  int vsize;
  int size;
  SysIOVec* iov;
  ErlDrvBinary** binv;

and does not define (because it is platform-dependant, but in this case we want Unix):

typedef struct {
    char *iov_base;
    size_t iov_len;

size is the total byte size of the whole ErlIOVec. vsize is the number of chunks (which may not match the number of binaries in the original iolist). iov and binv both are arrays of size vsize.

binv references the parent refc binaries (so the same binary can appear twice, and you may not be interested in all its data). Use this to manipulate the refcounter of the binary if you need to keep the data around.

iov is the chunks of data you are actually interested in. The two fields should be self-explanatory :)

Q: Is it OK to define the outputv callback and not output one ?
A: yes, you will receive an IOVec with a single chunk

Q: Why does open_port seemingly randomly fail with "bad argument" ? 
A: It will if you try to open a driver that is not loaded. Now, erl_ddll is smart and remembers who loaded which driver, and will unload a driver when it is not needed anymore. If you are testing from the shell, getting an uncaught exception will terminate the shell process and start a new one, which may cause your driver to get unloaded.

Q: Why is erl_ddll:load/2 asking for a path ? What is a reasonable value to provide ?
A: According to the OTP doc, the standard way is to use code:priv_dir(app_name)

jeudi 4 novembre 2010

Changement a chaud de la configuration d'ejabberd

Depuis le 1er novembre, mon serveur jabber (qui dessert aussi dispose du client web Jappix, qui nécessite un point de service BOSH. BOSH est supporté nativement par ejabberd, mais il n'était pas activé dans la configuration .

J'aurais pu redémarrer ejabberd, mais cela aurait provoqué quelques désagréments: déconnexion temporaire de tous les membres utilisant une addresse, et déconnexion de la chat-room officielle. Heureusement, il existe un moyen très simple d'ajouter le service à chaud.

Dans la configuration de ejabberd, il existe un tuple appelé "listen", qui liste un certain nombre de "listeners". Chaque listener est responsable d'un port tcp. Par exemple, par défaut, on trouve le listener suivant:

  {5222, ejabberd_c2s, [{access, c2s}, {shaper, cs2_shaper}, starttls, {certfile, "/etc/ssl/jabber.pem"}]}

Cela signifie qu'une instance du module ejabberd_c2s (le listener pour les connexions client) écoute sur le port 5222, avec une liste particulière d'options.

Le listener requis pour BOSH, que j'avais désactivé parce que je n'utilise pas l'interface d'administration web, est le suivant:

  {5280, ejabberd_http, [http_bind]}

Le listener web est un peu particulier, puisqu'il peut écouter sur un seul port et y fournir plusieurs services sur des chemins différents. L'option http_bind active le service BOSH sur l'addresse /http-bind.

Pour ajouter ce listener à chaud, il suffit de se connecter au noeud erlang; sur ma distribution favorite, il suffit d'un simple

  hostname # ejabberdctl debug

Qui nous donne un prompt erlang:


On démarre le listener supplémentaire:

    (ejabberd@hostname)1> ejabberd_listener:start_listener(5280, ejabberd_http, [http_bind]).

Et hop, ça fonctionne... plus qu'a quitter le shell erlang avec un double Ctrl-C !