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

Tuto - Try-with-resources et java.sql.SQLException: javax.resource.ResourceException: IJ000453: Unable to get managed connection
Florent TRIPIERMis à jour le 16 Oct 2014

Si vous connaissez la clause try-with-resources introduite par JavaLangage de développement très populaire ! 7, vous avez certainement été convaincu par les facilités quelle 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 linterface java.lang.AutoCloseable, ce qui inclut tous les flux implémentant linterface java.io.Closeable, il est possible de déclarer et dinstancier des variables de flux entre parenthèses entre le mot-clé try et laccolade ouvrant le bloc dinstructions. 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 dune application web Java EE tournant sur un serveur WildflyWildfly est un serveur d'application Java 8.0, cette exception fait assez peu de mystère.

Pour la datasource paramétrée dans le fichier standalone.xml, le serveur dapplication 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 laurai relâchée en fermant programmatiquement mon flux.

Ce que nous dit cette exception, cest quaucune connexion nest 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 lorsquun thread demande une connexion, il y a un timeout à atteindre avant que lexception 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. Cest é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 nest pas fermée.

Traquer lexception

Retrouver un flux mal fermé dans une application nest 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 loption debug sur le cached connection manager :


    <archive-validation enabled="true" fail-on-error="true" fail-on-warn="false"/>
    <bean-validation enabled="true"/>
    <default-workmanager>
       <short-running-threads>
          <core-threads count="50"/>
          <queue-length count="50"/>
          <max-threads count="50"/>
          <keepalive-time time="10" unit="seconds"/>
       </short-running-threads>
       <long-running-threads>
          <core-threads count="50"/>
          <queue-length count="50"/>
          <max-threads count="50"/>
          <keepalive-time time="10" unit="seconds"/>
       </long-running-threads>
   </default-workmanager>
   <cached-connection-manager debug="true"/>
</subsystem>

Il faut ensuite activer lutilisation du cached connection manager dans la datasource :

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

Le problème du try-with-resources

Dans notre application, nous nutilisons pas dAPIUne API est un programme permettant à deux applications distinctes de communiquer entre elles et d’échanger des données. 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 sattendre à ne pas rencontrer de problèmes de ce côté-là.

Cest 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 laccolade ouvrante, mais seulement si ils sont stockés dans une variable déclarée à cet endroit. Cest là quest la subtilité : à un seul endroit dans lapplication, 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à lapplication 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 nest pas une bonne solution puisquil finira par être entièrement bloqué quelle que soit sa taille. Doù linté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 lutiliser comme son fonctionnement le prévoit.