java.io.IOException: Empty document / JavaTool.getJavaDocument

Request description

Suite à l’import de module automatique via la fonctionnalité import-spec.yml, nous observons de nombreux cas d’Exception java.io.IOException: Empty document / JavaTool.getJavaDocument La solution de contournement appliquée consiste à réediter le code de l’objet concerné (ajout/suppression d’un espace par exemple) puis sauvegarder pour déclencher une recompilation dans le conteneur sur lequel le user (avec un rôle DESIGNER) est connecté. Cela permet de résoudre temporairement le problème dans le conteneur courant jusqu’au prochain démarrage d’un autre conteneur portant la même configuration. Ce problème est d’autant plus saillant que nous ne pouvons pas facilement accéder à un conteneur en particulier pour appliquer la procédure décrite et que la population des conteneurs est très changeante dans le contexte GKE/K8S. Je ne suis pas certain que la fonctionnalité import-spec.yml soit directement en cause mais les seuls objets concernés sont ceux des modules importés par ce moyen. Par contre, l’erreur survient au sein de conteneurs pour lesquels nous avons inhibé cette étape d’import-spec (en gros, nous essayons de faire en sorte que l’import spec soit réalisé par le premier conteneur qui démarre et que les suivants ne fassent rien car la configuration est partagée au sein d’un CloudSQL commun).

Nous observons ce comportement sur les version 5.2 (5.2.54 a priori à jour) et 5.3.28.

2024-03-22 11:59:31,582|SIMPLICITE|INFO||http://rcf-70786-app-5c5bd48bf4-8vn4f:8080||INFO|designer|com.simplicite.objects.System.ObjectInternal|partialClearCache||Event: Partial clear cache for object RenaultPerson
2024-03-22 11:59:31,548|SIMPLICITE|INFO||http://rcf-70786-app-5c5bd48bf4-8vn4f:8080||INFO|system|com.simplicite.util.engine.CoreCache|loadDynamicJar||Event: Loaded dynamic JARs
2024-03-22 11:59:31,548|SIMPLICITE|INFO||http://rcf-70786-app-5c5bd48bf4-8vn4f:8080||INFO|system|com.simplicite.util.engine.DynamicClassLoader|DynamicClassLoader||Event: Instanciate DynamicClassLoader@1ab9fc26
2024-03-22 11:59:29,518|SIMPLICITE|INFO||http://rcf-70786-app-5c5bd48bf4-8vn4f:8080||INFO|system|com.simplicite.util.engine.DynamicClassLoader|compile||Event: Compiling all classes from src:/usr/local/tomcat/webapps/ROOT/WEB-INF/src to bin:/usr/local/tomcat/webapps/ROOT/WEB-INF/bin
2024-03-22 11:59:29,121|SIMPLICITE|INFO||http://rcf-70786-app-5c5bd48bf4-8vn4f:8080||INFO|system|com.simplicite.util.tools.JavaTool|getClass||Event: Java class com.simplicite.objects.RenaultPerson.RenaultPerson compiled
2024-03-22 11:59:20,411|SIMPLICITE|ERROR||http://rcf-70786-app-5c5bd48bf4-8vn4f:8080||ECORESC001|system|com.simplicite.util.engine.ScriptAgent|prepareSource||Script error: RenaultPerson
    java.io.IOException: Empty document
     at com.simplicite.util.tools.JavaTool.getJavaDocument(JavaTool.java:795)
     at com.simplicite.util.engine.ScriptAgent.prepareSource(ScriptAgent.java:250)
     at com.simplicite.util.ScriptedObjectDB.prepareSource(ScriptedObjectDB.java:63)
     at com.simplicite.util.ScriptedObjectDB.postLoad(ScriptedObjectDB.java:114)
     at com.simplicite.util.engine.ConfigurationObject.postLoad(ConfigurationObject.java:40)
     at com.simplicite.objects.System.SimpleUser.postLoad(SimpleUser.java:54)
     at com.simplicite.util.ObjectHooks.postLoad(ObjectHooks.java:29)
     at com.simplicite.util.engine.ObjectLoader.load(ObjectLoader.java:105)
     at com.simplicite.util.engine.ObjectDirect.init(ObjectDirect.java:48)
     at com.simplicite.util.ObjectDB.init(ObjectDB.java:244)
     at com.simplicite.util.ObjectDB.load(ObjectDB.java:222)
     at com.simplicite.util.engine.CoreCache.instantiateObject(CoreCache.java:4110)
     at com.simplicite.util.engine.CoreCache.getObject(CoreCache.java:4043)
     at com.simplicite.util.engine.CoreCache.preCompileObject(CoreCache.java:459)
     at com.simplicite.util.engine.CoreCache.getObjectDefinition(CoreCache.java:3962)
     at com.simplicite.objects.System.ObjectInternal.initUpdate(ObjectInternal.java:514)
     at com.simplicite.util.ObjectHooks.initUpdate(ObjectHooks.java:79)
     at com.simplicite.util.ObjectContext.apply(ObjectContext.java:379)
     at com.simplicite.webapp.ObjectContextWeb.apply(ObjectContextWeb.java:291)
     ...
     at java.base/java.lang.Thread.run(Thread.java:833)

Steps to reproduce

This request concerns an up-to-date Simplicité instance
and these are the steps to reproduce it:

Technical information

Instance /health
---paste the content of your-instance.com/health---
Simplicité logs
---paste the content of the **relevant** server-side logs---
Browser logs
---paste content of the **relevant** browser-side logs---
Other relevant information

----E.g. type of deployment, browser vendor and version, etc.----

L’importspec dans un contexte de cluster de nœuds Simplicité redémarrant de manière arbitraire peut sans doute aboutir à des effets inattendus et imprevisibles.

La logique de l’import d’un module via un importspec c’est de comparer la version soumise vs la version en base, si la version soumise est > à la version en base, ça déclenche un import de modulensinon ça ne fait rien.

En l’état, Il n’ya pas de mécanisme d’ “exclusivité” entre les nœuds donc si plusieurs nœuds démarrent en même temps ils vont importer le module en // avec des effets imprévisibles.

Dans le même ordre d’idée, l’import de module est assez long et quand il est fait via importspec, la couche web attend et ne répond pas aux sollicitations, donc ça peut aboutir à des “kills” abusifs de nœuds en plein milieu d’un import. etc.

Bref, importer un nouveau module ou une nouvelle version du module n’est pas un usage de “régime établi”, c’est une opération d’installation pour laquelle il faut prévoir un processus ad hoc garantissant que les choses se passent bien: ne démarrer qu’un noeud et le laisser démarrer jusqu’au bout.

Bonjour David,

Merci beaucoup pour ton retour rapide.

Je confirme que le paramétrage de la chaîne de déploiement (le CD du CI/CD) prend bien en compte le temps de chargement des imports et que le premier conteneur a bien de temps de démarrer avant que les autres ne commencent à démarrer. Le problème se produit aussi dans le cas particulier des environnements de développement dont les services sont mono réplicas et dont les démarrages sont par construction lancé dans une séquence ordonnée déterminée :

  1. d’abord le service dédié aux clients api,
  2. puis le service dédié aux clients UI,
  3. puis optionnellement le cas échéant le service dédié aux usages cron/batch

Chaque service (dimensionné sur mesure) ne commence à démarrer qu’après que le précédent ait terminé de démarrer et soit effectivement utilisable.

Je précise à toutes fins utiles qu’un Clear cache global (réparti sur les différents services / réplicas via l’action CheckClearCache) permet aussi dans la plupart des cas de résoudre le problème au sein de chaque conteneur. La première solution étant préférée car elle résous le problème sans déconnecter les utilisateurs.

Concentrons nous sur la cas des envs de dev qui semblent plus “simples”.

Sur quel critère technique considérez vous qu’une instance est totalement démarrée ?

Serait il possible de faire le test d’un démarrage avec importspec sur un vrai environnement mono-instance = 1 et 1 seul nœud Simplicité ?

L’idée derrière ces 2 questions est de voir si les pbs rencontrés sont éventuellement liés à la coexistence de plusieurs noeuds n’ayant pas forcément assez attendu pour démarrer.

Sinon, sur le message “Empty document”, y-a-t-il d’autres message d’erreur avant ? Genre “Doc error” ? Je pose la question pour essayer de mieux cerner dans quel cas ça pourrait se produire (le message “Empty document” est, en effet, lié à un try/catch d’assez haut niveau et peut donc être induit à plusieurs endroits plus bas niveau).

Serait il possible d’avoir un extrait de log plus complet pour mieux comprendre à quel moment du démarrage ce pb se produit ?

Bonjour David,

Merci encore pour ton support et pour ton aide.

J’ai revu nos chaînes CI/CD pour relocaliser les opérations d’import dans un pod dédié au “setup” mono réplica et déployés puis purgé avant que les autres services ne soient déployés. Les autres services (api/app/cron) démarrent sans importer aucun module (ni import-spec ni ZIP dans le dossier modules).

→ Les erreurs Empty document ne sont en effet plus observées pour l’instant.

/!\ La période d’observation est courte car je n’ai redéployé les services que depuis hier soir mais cela concerne 90 services (3 services - api|app|cron - sur 3 environnements - dev|int|ope - et 10 namespaces).

Sur un de ces environnements, j’ai activé la feature ModuleAutoUpdate (Module → Mise à jour automatique → “Oui” pour mes modules communs et activé la tâche cron).

→ Sur ce groupe de 3 services, il y en a 2 - app et cron - sur lesquels j’observe à nouveau des erreurs Empty document depuis que la tâche ModuleAutoUpdate a été exécutée à 4h ce matin.

OK si un seul noeud execute l’import par importspec ça devrait aller (on fait ce genre de choses en permanence, s’il y avait un pb de ce type on l’aurait vu passer).

Pour améliorer les choses on va regarder pour ajouter un principe de lock à la manière du lock de la cron pour s’assurer qu’un seul noeud effectue les imports (ça peut devenir rapidement compliqué si on doit prévoir le cas où un noeud serait tué en plein pendant l’import, donc pour faire simple on va laisser cas de coté pour le moment)

Tu ne m’as pas répondu sur ce qui est utilisé comme critère pour considérer qu’une instance est totalement démarrée. je pose la question car c’est un point d’attention à avoir vs la conception du lock.

PS: le flag de mise à jour auto des modules était une ancienne feature d’avant les importspecs. Ca permettait de forcer le réimport de modules via une tâche cron dans une logique de “continuous delivery” basique de l’époque (NB: c’est pas aussi subtil que l’import spec car ça ne checke pas la version pour vérifier que re réimport est nécessaire ou pas). Bref rien à voir avec la mécanique de l’importspec et c’est assez obsolète vs les usages actuels CI/CD, donc plutôt à éviter.

L’instance est considérée comme démarrée dès lors qu’elle répond au ping. D’après mes observations, ça correspond au log “Webapp is ready” :

Voici la pipeline utilisée jusqu’à vendredi qui induit les “Empty document” :

  1. Un job “api-deploy” démarre, il contient les spécifications d’import (ce job démarre en fait 2 réplicas en parallèle qui font chacun l’import des modules dans leur conteneur mais sur une même base partagée)
  2. Les jobs “app-deploy” et “cron-deploy” démarrent lorsque “api-deploy” est totalement démarré (les 2 réplicas)

Voici la dernière pipeline telle que reconfigurée ce WE :

  1. Un job “setup-deploy” démarre, il contient les spécifications d’import (ce job ne démarre qu’un seul réplica)
  2. Un job “setup-purge” est déclenché manuellement par l’opérateur du pipeline après contrôle du bon fonctionnement du service “setup”
  3. Les jobs “api-deploy”, “app-deploy” et “cron-deploy” démarrent lorsque “setup-purge” est terminé. Les jobs “api-deploy” et “app-deploy” démarrent chacun 2 réplicas, le job “cron-deploy” ne démarre qu’un réplica.

Avec cette dernière configuration, je n’ai pas observé de nouveau cas de “Empty document” sauf dans le cas où j’ai activé “ModuleAutoUpdate”.

C’est noté. Je vais donc désactiver tout ça… Le job “setup” de notre nouvelle pipeline couvrira a priori les besoins. Si les services ne sont pas précédemment déployés, l’import des modules sera appliqué en amont de leur déploiement et s’ils sont déjà déployés, le déploiement du job “setup” suivi d’un clear cache global initié sur l’instance “setup” rafraichira tous les autres services. Ainsi, le PL (Product Leader) qui opère la pipeline conserve la main de bout en bout.

La logique de l’import spec est basique : si l’importpec indique d’importer un module X en version x et que ce module X n’existe pas ou existe mais dans une version y < x le module est importé, sinon il ne fait rien.

Pour utiliser l’importspec il faut donc avoir une gestion extrêmement rigoureuse des numéro de versions de ses modules.

NB: La comparaison de versions se fait au sens d’une comparaison de “semver”
1.2.2 < 1.2.3
1.2 < 1.2.3
1.3 > 1.2.3
etc.
On conseille néanmoins de toujours utiliser le même nombre de digits pour ses numéros de version

Merci, j’ai taggé ton précédent post comme solution…

On te tiendra au courant pour ce qui est du lock exclusif d’imporspec

Si ça vous semble toujours pertinent, je veux bien. Merci. Mais je confirme qu’a priori la reconfiguration de la pipeline via l’étape “setup” mono-réplica a résolu le pb identifié dans le post.

Oui comme dit dans ma première réponse c’est toujours mieux d’être déterministe sur les opérations d’installation vs un simple redémarrage en régime établi

This topic was automatically closed 60 minutes after the last reply. New replies are no longer allowed.