Fuite de threads dans Jonas

Depuis plusieurs mois nous avions détecté que le nombre de thread de notre serveur jonas augmentait sans arrêt. Un de mes collègues a donc pris le taureau par les cornes pour trouver l’origine du problème. Après 4 jours à fouiller dans le code source de Jonas, il a trouvé le problème au niveau du connecteur Joram (notre application utilise des JMS).

Dans la classe

org.objectweb.joram.client.connector.ManagedConnectionFactoryImpl, on peut lire :

public Set getInvalidConnections(Set connectionSet) throws ResourceException {
  if (AdapterTracing.dbgAdapter.isLoggable(BasicLevel.DEBUG))
    AdapterTracing.dbgAdapter.log(BasicLevel.DEBUG,
       this+" getInvalidConnections("+connectionSet+")");
    Iterator it = connectionSet.iterator();
    ManagedConnectionImpl managedCx;
    while (it.hasNext()) {
      try {
        managedCx = (ManagedConnectionImpl) it.next();
        if (managedCx.isValid())
          connectionSet.remove(managedCx);
        }
      catch (ClassCastException exc) {}
  }
  return connectionSet;
}

Ca met en évidence deux règles de codage Java importantes qui ne sont pas respectées :

  1. Ne pas retourner la référence d’un paramètre en valeur de retour
  2. Eviter de modifier un paramètre d’une méthode
La méthode getInvalidConnections permet de chercher dans la liste de connexions passées en paramètre celles qu’il faut libérer. Mais au lieu de mettre les connexions invalides dans une nouvelle collection, ils suppriment les connexions valides de la collection passée en paramètre. Du coup ces connexions ne sont plus dans la liste des connexions actives, elles ne seront donc jamais libérées.

Depuis que nous avons patché le connecteur Joram, nous n’avons plus de fuite de thread :)

Le patch :

public Set getInvalidConnections(Set connectionSet) throws ResourceException {
  if (AdapterTracing.dbgAdapter.isLoggable(BasicLevel.DEBUG))
    AdapterTracing.dbgAdapter.log(BasicLevel.DEBUG,
      this+" getInvalidConnections("+connectionSet+")");
  Iterator it = connectionSet.iterator();
  ManagedConnectionImpl managedCx;
  java.util.HashSet invalidConnections = new java.util.HashSet();
  while (it.hasNext()) {
    try {
      managedCx = (ManagedConnectionImpl) it.next();
      if (!managedCx.isValid())
        invalidConnections.add(managedCx);
    }
    catch (ClassCastException exc) {}
  }
  return invalidConnections;
}