AXOPEN

Try-with-resources et java.sql.SQLException: javax.resource.ResourceException: IJ000453: Unable to get managed connection

Si vous connaissez la clause try-with-resources introduite par Java 7, vous avez certainement été convaincu par les facilités qu’elle offre quant à la gestion de la fermeture des flux (ainsi que par la diminution de la verbosité de votre code). Un petit rappel toutefois pour ceux qui ne connaîtraient pas…

Try-with-resources

Le principe est le suivant : pour les flux implémentant l’interface java.lang.AutoCloseable, ce qui inclut tous les flux implémentant l’interface java.io.Closeable, il est possible de déclarer et d’instancier des variables de flux entre parenthèses entre le mot-clé try et l’accolade ouvrant le bloc d’instructions. Dans ces conditions, la fermeture des flux ainsi déclarés est gérée par la JVM, sans que vous ayez besoin de le faire explicitement dans un bloc finally.

Par exemple, pour écrire dans un fichier, cela nous donne :

try (FileOutputStream fos = new FileOutputStream(new File("/var/monfichier.txt"))) {
    // votre code
} catch (IOException e) {
    e.printStackTrace();
}

 Unable to get managed connection

Revenons à nos moutons : javax.resource.ResourceException: IJ000453: Unable to get managed connectionfor java:/my-datasource. Nous situant dans le contexte d’une application web Java EE tournant sur un serveur Wildfly 8.0, cette exception fait assez peu de mystère.

Pour la datasource paramétrée dans le fichier standalone.xml, le serveur d’application a créé un pool de connexions. Chaque fois que je demande une connexion pour créer un PreparedStatement, il va en chercher une dans ce pool. La connexion en question ne sera de nouveau disponible dans le pool que lorsque je l’aurai relâchée en fermant programmatiquement mon flux.

Ce que nous dit cette exception, c’est qu’aucune connexion n’est disponible dans mon pool, autrement dit toutes les connexions sont occupées. Il faut savoir que la taille par défaut du pool de connexions sur Wildfly est de 10 à 20 connexions, et également que lorsqu’un thread demande une connexion, il y a un timeout à atteindre avant que l’exception soit propagée.

Cela signifie que pour que cette exception soit levée il faut que toutes mes connexions soient occupées simultanément (donc dans des threads séparés) sur des traitements plus longs que le timeout de connexion. C’est évidemment improbable et le diagnostic le plus plausible est que des connexions ne sont jamais fermées. Dans ce cas, cette exception peut très vite apparaître : en effet, il suffit de passer 10 ou 20 fois dans un code où une connexion n’est pas fermée.

Traquer l’exception

Retrouver un flux mal fermé dans une application n’est pas forcément facile. Heureusement, le gestionnaire de connexions de Wildfly, JCA, propose des options de debug.

Dans le fichier standalone.xml, dans le subsystem JCA, il faut activer l’option debug sur le cached connection manager :


    
    
    
       
          
          
          
          
       
       
          
          
          
          
       
   
   

Il faut ensuite activer l’utilisation du cached connection manager dans la datasource :


De cette manière, vous aurez des exceptions dans les logs lorsqu’une connexion sera laissée ouverte.

Le problème du try-with-resources

Dans notre application, nous n’utilisons pas d’API de persistence telle que JPA, mais nous gérons notre couche modèle directement en JDBC. Or toutes nos connexions sont instanciées via la clause try-with-resources. On peut donc s’attendre à ne pas rencontrer de problèmes de ce côté-là.

C’est pourtant bien ici que se trouve la solution : nous avons fait une mauvaise utilisation du try-with-resources. Certes il ferme bien les flux ouverts dans les parenthèses entre le mot try et l’accolade ouvrante, mais seulement si ils sont stockés dans une variable déclarée à cet endroit. C’est là qu’est la subtilité : à un seul endroit dans l’application, nous avons écrit :

// mDataSource est la datasource que nous nous sommes injectée
try (PreparedStatement lPreparedStatement = mDataSource.getConnection().prepareStatement(lQuery);) {
    // votre code
} catch (SQLException e) {
    e.printStackTrace();
}

au lieu de :

// mDataSource est la datasource que nous nous sommes injectée
try (Connection lConnection = mDataSource.getConnection();
    PreparedStatement lPreparedStatement = lConnection.prepareStatement(lQuery);) {
    // votre code
} catch (SQLException e) {
    e.printStackTrace();
}

De ce fait, la connexion n’était jamais fermée, donc jamais rendue au pool, donc perdue. Accéder 20 fois à la page où ce code est appelé et le pool entier est perdu.

Conclusion

Il faut bien saisir qu’à ce stade là l’application entière est neutralisée (ou du moins toutes les pages et fonctionnalités nécessitant un accès en base de données). Evidemment, resizer le pool de connexions n’est pas une bonne solution puisqu’il finira par être entièrement bloqué quelle que soit sa taille. D’où l’intérêt des outils de debug exposés plus haut et de la bonne utilisation du try-with-resources. Ce dernier reste une belle innovation, il faut simplement l’utiliser comme son fonctionnement le prévoit.