88 votes

Jenkins Pipeline NotSerializableException : groovy.json.internal.LazyMap

Résolu : Merci à réponse ci-dessous de S.Richmond. J'avais besoin de débloquer tous les cartes stockées de l groovy.json.internal.LazyMap ce qui signifie l'annulation des variables envServers et object après utilisation.

Supplémentaire : Les personnes recherchant cette erreur pourraient être intéressées par l'étape du pipeline Jenkins. readJSON à la place - trouver plus d'informations ici .


J'essaie d'utiliser Jenkins Pipeline pour prendre les données de l'utilisateur qui sont transmises au travail sous forme de chaîne json. Pipeline analyse ensuite cette chaîne à l'aide du slurper et en extrait les informations importantes. Il utilisera ensuite ces informations pour exécuter un travail plusieurs fois en parallèle avec des paramètres de travail différents.

Jusqu'à ce que j'ajoute le code ci-dessous "## Error when below here is added" le script fonctionnera bien. Même le code en dessous de ce point s'exécutera seul. Mais lorsqu'il est combiné, j'obtiens l'erreur suivante.

Je dois noter que le job déclenché est appelé et s'exécute avec succès mais que l'erreur ci-dessous se produit et fait échouer le job principal. Pour cette raison, le travail principal n'attend pas le retour du travail déclenché. I pourrait try/catch autour de la build job: mais je veux que le travail principal attende que le travail déclenché se termine.

Quelqu'un peut-il m'aider ? Si vous avez besoin de plus d'informations, faites-le moi savoir.

Cheers

def slurpJSON() {
return new groovy.json.JsonSlurper().parseText(BUILD_CHOICES);
}

node {
  stage 'Prepare';
  echo 'Loading choices as build properties';
  def object = slurpJSON();

  def serverChoices = [];
  def serverChoicesStr = '';

  for (env in object) {
     envName = env.name;
     envServers = env.servers;

     for (server in envServers) {
        if (server.Select) {
            serverChoicesStr += server.Server;
            serverChoicesStr += ',';
        }
     }
  }
  serverChoicesStr = serverChoicesStr[0..-2];

  println("Server choices: " + serverChoicesStr);

  ## Error when below here is added

  stage 'Jobs'
  build job: 'Dummy Start App', parameters: [[$class: 'StringParameterValue', name: 'SERVER_NAME', value: 'TestServer'], [$class: 'StringParameterValue', name: 'SERVER_DOMAIN', value: 'domain.uk'], [$class: 'StringParameterValue', name: 'APP', value: 'application1']]

}

Erreur :

java.io.NotSerializableException: groovy.json.internal.LazyMap
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569)
    at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
    at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
    at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50)
    at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179)
    at java.io.ObjectOutputStream.writeObject(Unknown Source)
    at java.util.LinkedHashMap.internalWriteEntries(Unknown Source)
    at java.util.HashMap.writeObject(Unknown Source)
...
...
Caused by: an exception which occurred:
    in field delegate
    in field closures
    in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c

0 votes

Je viens juste de rencontrer ce problème moi-même. Avez-vous fait des progrès ?

4 votes

136voto

luka5z Points 3430

Utilisez JsonSlurperClassic à la place.

Depuis Groovy 2.3 ( note : Jenkins 2.7.1 utilise Groovy 2.4.7 ) JsonSlurper renvoie à LazyMap au lieu de HashMap . Cela rend la nouvelle mise en œuvre de JsonSlurper pas fil sûr et pas sérialisable. Cela le rend inutilisable en dehors des fonctions @NonDSL dans les scripts DSL de pipeline.

Cependant, vous pouvez vous rabattre sur groovy.json.JsonSlurperClassic qui prend en charge les anciens comportement et peuvent être utilisés en toute sécurité dans des scripts de pipeline.

Exemple

import groovy.json.JsonSlurperClassic 

@NonCPS
def jsonParse(def json) {
    new groovy.json.JsonSlurperClassic().parseText(json)
}

node('master') {
    def config =  jsonParse(readFile("config.json"))

    def db = config["database"]["address"]
    ...
}    

ps. Vous devez encore approuver JsonSlurperClassic avant qu'il puisse être appelé.

2 votes

Pourriez-vous me dire comment approuver JsonSlurperClassic ?

7 votes

L'administrateur de Jenkins devra naviguer vers Manage Jenkins " In-process script Approval.

0 votes

Malheureusement, je ne reçois que hudson.remoting.ProxyException: org.codehaus.groovy.control.MultipleCompilationErrorsExcepti‌​on: startup failed: Script1.groovy: 24: unable to resolve class groovy.json.JsonSlurperClassic

78voto

S.Richmond Points 2849

J'ai rencontré ce problème aujourd'hui et, grâce à un peu de force brute, j'ai trouvé comment le résoudre et potentiellement pourquoi.

Il est probablement préférable de commencer par le pourquoi :

Jenkins a un paradigme où tous les travaux peuvent être interrompus, mis en pause et repris par le redémarrage du serveur. Pour y parvenir, le pipeline et ses données doivent être entièrement sérialisables, c'est-à-dire qu'ils doivent être capables de sauvegarder l'état de tous les éléments. De même, il doit être capable de sérialiser l'état des variables globales entre les nœuds et les sous-travaux dans la construction, ce qui est ce qui se passe pour vous et moi et pourquoi cela ne se produit que si vous ajoutez cette étape de construction supplémentaire.

Pour une raison quelconque, les JSONObjects ne sont pas sérialisables par défaut. Je ne suis pas un développeur Java et je ne peux donc pas en dire beaucoup plus sur le sujet, malheureusement. Il existe de nombreuses réponses sur la façon de résoudre ce problème, mais je ne sais pas si elles sont applicables à Groovy et Jenkins. Voir ce billet pour un peu plus d'informations.

Comment le réparer :

Si vous savez comment faire, vous pouvez éventuellement rendre le JSONObject sérialisable d'une manière ou d'une autre. Sinon, vous pouvez résoudre le problème en vous assurant qu'aucune variable globale n'est de ce type.

Essayez de désactiver votre object ou en l'enveloppant dans une méthode pour que sa portée ne soit pas globale.

2 votes

Merci, c'est l'indice dont j'avais besoin pour résoudre ce problème. Alors que j'avais déjà essayé votre suggestion, cela m'a fait regarder à nouveau et je n'avais pas considéré que je stockais des parties de la carte dans d'autres variables - celles-ci causaient les erreurs. Je devais donc les désactiver également. Je vais modifier ma question pour inclure les changements corrects dans le code. Au revoir

2 votes

Il est consulté ~8 fois par jour. Pourriez-vous fournir un exemple plus détaillé de la façon de mettre en œuvre cette solution ?

1 votes

Il n'y a pas de solution simple car cela dépend de ce que vous avez fait. Les informations fournies ici ainsi que la solution ajoutée par @Sunvic en haut de son post devraient être suffisantes pour amener quelqu'un à trouver une solution pour son propre code.

16voto

Mike Kobit Points 739

EDIT : Comme l'a souligné @Sunvic dans les commentaires, la solution ci-dessous ne fonctionne pas telle quelle pour les tableaux JSON.

J'ai réglé ce problème en utilisant JsonSlurper et ensuite créer un nouveau HashMap des résultats de la paresse. HashMap est Serializable .

Je crois que cela a nécessité la mise en place d'une liste blanche pour les deux éléments suivants new HashMap(Map) et le JsonSlurper .

@NonCPS
def parseJsonText(String jsonText) {
  final slurper = new JsonSlurper()
  return new HashMap<>(slurper.parseText(jsonText))
}

Dans l'ensemble, je recommanderais d'utiliser simplement l'option Étapes des services publics de pipelines plugin car il a un readJSON étape qui peut prendre en charge soit des fichiers dans l'espace de travail, soit du texte.

1 votes

Cela n'a pas fonctionné pour moi - j'ai eu une erreur. Could not find matching constructor for: java.util.HashMap(java.util.ArrayList) . La documentation suggère de renvoyer une liste ou une carte - comment configurer le renvoi d'une carte ?

0 votes

@Sunvic Bien vu, les données que nous avons analysées sont toujours des objets, jamais des tableaux JSON. Essayez-vous d'analyser un tableau JSON ?

0 votes

Ah oui, c'est un tableau JSON, ça doit être ça.

5voto

TomDotTom Points 41

Une forme légèrement plus généralisée de la réponse de @mkobit, qui permettrait de décoder les tableaux ainsi que les cartes, serait la suivante :

import groovy.json.JsonSlurper

@NonCPS
def parseJsonText(String json) {
  def object = new JsonSlurper().parseText(json)
  if(object instanceof groovy.json.internal.LazyMap) {
      return new HashMap<>(object)
  }
  return object
}

NOTE : Sachez que cette opération ne convertit que l'objet LazyMap de niveau supérieur en HashMap. Tous les objets LazyMap imbriqués seront toujours là et continueront à causer des problèmes avec Jenkins.

2voto

Marcin Płonka Points 794

La façon dont le plugin pipeline a été implémenté a des implications assez sérieuses pour le code Groovy non trivial. Ce lien explique comment éviter les problèmes possibles : https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

Dans votre cas spécifique, j'envisagerais d'ajouter @NonCPS à l'annotation slurpJSON et en retournant une carte de cartes au lieu d'un objet JSON. Non seulement le code est plus propre, mais il est aussi plus efficace, surtout si le JSON est complexe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X