Jenkins Pipeline手記(1)-CPSプログラミングとは

9873 ワード

引用する
最近、Jenkinsを使用した継続的な統合タスクで、次のような問題が発生しました.
java.io.NotSerializableException: java.util.regex.Matcher

これはJenkinsのCPSプラグインが報告したもので,正規表現に関連する操作の関数を用いた場合,コードの書き方が規範化されず,シーケンス化できないためであることが分かった.ではCPSとは何か、Jenkinsはなぜシーケンス化の操作が必要なのか.
CPSとは
関数式プログラミングでは、CPS (Continuation-Passing Style)は、すべての制御ブロックがcontinuationによって明示的に伝達されるプログラミングスタイルです.簡単に言えば、CPSスタイルでは、関数に戻り文がありません.その呼び出し者がその結果を得るには、結果を取得して実行を続けるためにコールバック関数を明示的に伝達する必要があります.プログラム全体が実行されることを保証するために、このコールバック関数はまたずっとネストされます.ここでのコールバック関数は「continuation」です.しばらく良い翻訳が見つからないので、その原文を残します.
一般的な数学的乗算を計算する関数など、再帰的な例を見てみましょう.
//     
function fact(n) {
  if (n == 0)
    return 1 ;
  else
    return n * fact(n-1) ;
}

// CPS    
function fact(n,ret) {
  if (n == 0)
    ret(1) ;
  else
    fact(n-1, function (t0) {
     ret(n * t0) }) ;
}

//     
fact (5, function (n) {
  console.log(n) ; // Output 120
})


上は伝統的な再帰的実現であり,下はCPSスタイルの実現である.return文はコールバック関数の呼び出しに置き換えられ、この動作に乗算すると、終了条件まで次の再帰でネストされたコールバック関数によって完了することがわかります.各層の再帰では、コールバック関数はパラメータに1つの係数を乗算し、最外層の印刷結果まで上位層のコールバック関数を呼び出すことが理解できます.
CPSの応用シーン
1.Jenkinsでの応用は最初の問題に戻り、JenkinsはなぜCPSプログラミング技術を使うのか.なぜならJenkins Pipelineの実装では,コードの実行をいつでも中断し,状態を保存し,適切なタイミングで実行を再開することが期待されるからである.Jenkins agentのダウンタイムに対応できます.1つの関数が実行された後に返されると,一部の状態が失われ,CPSコードは途中で結果を返さないため,この問題を解決できる.
しかし、あなたもPipelineコードはGroovy言語で、それ自体はCPSスタイルではありません.これは解釈器がコードをCPSスタイルにコンパイルする必要があります.Jenkinsの中でgroovy-cpsというプラグインで完成します.このプラグインの安定性を見て、CPSとGroovy解釈器についてもっと紹介することもできます.
オブジェクトの状態を永続化するにはでは、このオブジェクトは必要に応じてシーケンス化できます.(Serializable).Jenkins Pipelineの状態を保存するには、CPSのコードも直列化することが求められます.いくつかのコードが直列化できない場合、または直列化が安全でない場合は、Pipelineコードに@NonCPSのタグを付けることができます.また、このタグを明らかにした関数は、他のタグのある関数しか呼び出せません.Jenkinsはこのような関数に対して数は通常のコンパイルを行い、シーケンス化可能なCPSコードは生成されない.
正規表現に関連する機能を書くときに使用される可能性のある~オペレータは、シーケンス化が安全ではないため、@NonCPSというタグを宣言します.
def t = (text =~ TEST_REGEX)

上記の文の方法を使用する前にマークを宣言するのを忘れた場合、文章の先頭にあるシーケンス不可能な異常が投げ出され、私が遭遇した問題の原因はここにあります.
2.非同期リクエストに適用するもう一つの考えやすいシーンは非同期リクエストであり、JavaScriptコードではリクエストの結果をコールバック関数で処理し、プログラムを停止させないようにすることがよくあります.これはCPSスタイルです
request("./index", function (text) {
 document.getElementById("test").innerHTML = text ;
}) ;

request関数の実装では、Ajaxオブジェクトを使用します.
function request (url, onSuccess, onFail) {
 
  var req ;
  function process() {
    if (req.readyState == 4) {
      if (req.status == 200) {
        if (onSuccess)
          onSuccess(req.responseText, url, req) ;
      } else {
        if (onFail)
          onFail(url, req) ;
      }
    }
  }
  if (window.XMLHttpRequest)
    req = new XMLHttpRequest();
  req.onreadystatechange = process;
  req.open("GET", url, async);
  req.send(null);
 
  return req ;
}


このうちコールバック関数onSuccessonFailがいわゆるcontinuationである.
参考資料
  • http://matt.might.net/articles/by-example-continuation-passing-style/
  • https://github.com/jenkinsci/workflow-cps-plugin
  • https://github.com/cloudbees/groovy-cps/
  • https://en.wikipedia.org/wiki/Continuation-passing_style