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 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…
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();
}
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 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 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.
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 :
<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 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.
Dans notre application, nous n’utilisons pas d’APIUne 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 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.
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.
Frameworks : le comparatif entre React et Angular
Mettre à jour sa stack applicative, c'est assurer la stabilité et la sécurité de ses applications. Il est donc important de faire le suivi des mises à jour pour ne pas se retrouver bloqué à cause de l'accumulation de la dette technique. Sur Symfony, les versions majeures (X.0.0) sont programmées tous les 2 ans, et les versions mineures (1.X.0) sont programmées tous les 6 mois, en mai et en novembre. Chaque version arrive avec son lot de nouveautés qu'il est important de prendre en compte. Les dates de mises à jour étant connues, l'intégration à des Roadmap est alors simplifiée. Mais à quoi faut-il penser lors de ces migrations ?
Ansible est une solution qui permet de faire principalement du déploiement automatisé et de la configuration automatisée. Ansible tourne avec Python, Powershell et Ruby et sert à automatiser la gestion des serveurs…