Jenkins CPS Serialization Visualization

Normal Execution (No Restart)

Pipeline Execution
    │
    ▼
┌─────────────────┐
│ Stage 1: Build  │ ✓ Complete
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Stage 2: Test   │ ✓ Complete
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Stage 3: Deploy │ ✓ Complete
└─────────────────┘
    │
    ▼
  SUCCESS

When Jenkins Restart Occurs

Pipeline Execution
    │
    ▼
┌─────────────────┐
│ Stage 1: Build  │ ✓ Complete
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Stage 2: Test   │ ⏳ Running...
└─────────────────┘
    │
    │   💥 Jenkins Restart!
    │
    ▼
╔═══════════════════════════════════════════════════════════════╗
║  1. Serialize                                                 ║
║                                                               ║
║  Memory State:                      Disk (program.dat):       ║
║  ┌─────────────────────┐           ┌─────────────────────┐   ║
║  │ currentStage = 2    │  ──────▶  │ 01001010 01100101   │   ║
║  │ testResult = null   │   Save    │ 01101110 01101011   │   ║
║  │ buildArtifact = ... │           │ 01101001 01101110   │   ║
║  └─────────────────────┘           └─────────────────────┘   ║
║                                                               ║
╚═══════════════════════════════════════════════════════════════╝
    │
    │   🔄 Jenkins Restart Complete
    │
    ▼
╔═══════════════════════════════════════════════════════════════╗
║  2. Deserialize                                               ║
║                                                               ║
║  Disk (program.dat):               Memory Restored:           ║
║  ┌─────────────────────┐           ┌─────────────────────┐   ║
║  │ 01001010 01100101   │  ──────▶  │ currentStage = 2    │   ║
║  │ 01101110 01101011   │  Restore  │ testResult = null   │   ║
║  │ 01101001 01101110   │           │ buildArtifact = ... │   ║
║  └─────────────────────┘           └─────────────────────┘   ║
║                                                               ║
╚═══════════════════════════════════════════════════════════════╝
    │
    ▼
┌─────────────────┐
│ Stage 2: Test   │ ◀── Resume from here!
└─────────────────┘
    │
    ▼
┌─────────────────┐
│ Stage 3: Deploy │
└─────────────────┘
    │
    ▼
  SUCCESS

Serializable vs Non-Serializable

┌─────────────────────────────────────────────────────────────────┐
│                    Serializable ✓                               │
│                    (Can be saved/restored)                      │
│                                                                 │
│   String name = "build-123"     ──▶  Save ──▶  Restore ──▶ OK  │
│   int count = 5                 ──▶  Save ──▶  Restore ──▶ OK  │
│   List<String> files = [...]    ──▶  Save ──▶  Restore ──▶ OK  │
│   Map<String, Object> config    ──▶  Save ──▶  Restore ──▶ OK  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Non-Serializable ✗                           │
│                    (Cannot be saved/restored)                   │
│                                                                 │
│   Socket connection = ...       ──▶  Save ──▶  💥 ERROR        │
│   InputStream stream = ...      ──▶  Save ──▶  💥 ERROR        │
│   Database db = ...             ──▶  Save ──▶  💥 ERROR        │
│   Iterator iter = ...           ──▶  Save ──▶  💥 ERROR        │
│                                                                 │
│   Reason: "Current connection state" cannot be saved           │
│           to a file and restored later                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Application in This Codebase

┌─────────────────────────────────────────────────────────────────┐
│  HttpApiService (src/service/)                                  │
│                                                                 │
│  class HttpApiService {                                         │
│      def jenkinsfile  ◀── Must be Serializable                 │
│      def logger       ◀── Must be Serializable                 │
│                                                                 │
│      def executeRequest(...) {                                  │
│          HttpClient httpClient = HttpClients.createDefault()    │
│          //       ▲                                             │
│          //       │                                             │
│          //  Local variable - OK                                │
│          //  Disappears when method ends                        │
│          //  Jenkins doesn't need to save it                    │
│      }                                                          │
│  }                                                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│  shellScriptHelper (vars/)                                      │
│                                                                 │
│  def call(Closure shellScriptClosure, List args = []) {         │
│      sh(shMap)  ◀── Direct pipeline step call                  │
│  }              │                                               │
│                 │                                               │
│                 └── Must be in vars/ to call pipeline           │
│                     steps from CPS context                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Pipeline Step Execution Context

The second CPS constraint: Where the pipeline step is called matters.

┌─────────────────────────────────────────────────────────────────┐
│  Rules                                                          │
│                                                                 │
│  vars/ → direct pipeline step call     ✓ OK                    │
│  src/  → direct pipeline step call     ✗ Not allowed           │
│  src/  → vars/ wrapper → pipeline step ✓ OK                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Pattern: Indirect Call via vars/

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  src/HttpApiService.groovy (Outside CPS context)                │
│  ┌─────────────────────────────────────────┐                   │
│  │ logger.stepProcessing("msg")            │                   │
│  │         │                               │                   │
│  │         ▼                               │                   │
│  └─────────┼───────────────────────────────┘                   │
│            │                                                    │
│            │  Call                                              │
│            ▼                                                    │
│  vars/logger.groovy (Inside CPS context)                        │
│  ┌─────────────────────────────────────────┐                   │
│  │ void stepProcessing(String description) │                   │
│  │     echo "🏃 ${description}"  ✓ OK     │                   │
│  │           ▲                             │                   │
│  │           │                             │                   │
│  │     echo executes here                  │                   │
│  └─────────────────────────────────────────┘                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

What Doesn't Work

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  src/HttpApiService.groovy (Outside CPS context)                │
│  ┌─────────────────────────────────────────┐                   │
│  │ jenkinsfile.sh("echo hello")  ✗        │                   │
│  │              │                          │                   │
│  │              ▼                          │                   │
│  │         💥 Problem                      │                   │
│  │                                         │                   │
│  │  sh() called directly from src/         │                   │
│  │  → Pipeline step outside CPS context    │                   │
│  │  → Jenkins cannot handle properly       │                   │
│  └─────────────────────────────────────────┘                   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Correct Pattern in This Codebase

// vars/logger.groovy (Inside CPS context)
void stepProcessing(String description) {
    echo "🏃 ${description}"  // ✓ echo called here - OK
}

// src/HttpApiService.groovy (Outside CPS context)
class HttpApiService {
    def logger

    HttpApiService(def jenkinsfile) {
        this.logger = jenkinsfile.logger
    }

    def doSomething() {
        logger.stepProcessing("msg")  // ✓ Delegates to vars/ - OK
        // jenkinsfile.sh("...")      // ✗ Direct call - Not allowed
    }
}

Component Placement

Component Location Reason
HttpApiService src/service/ Apache HttpClient (pure Java). logger called indirectly via vars/
shellScriptHelper vars/ Direct sh() call
bitbucketApiHelper vars/ Direct withCredentials() call
logger vars/ Direct echo call

Summary

Constraint Description
Serializable Objects must be savable as bytes for restoration after restart
Pipeline Step Context sh, echo, withCredentials, etc. can only be called directly from vars/
Scenario Result
All variables are Serializable Pipeline continues after Jenkins restart ✓
Non-Serializable variable exists NotSerializableException error 💥
Direct pipeline step call from src/ CPS problem 💥
Indirect call via vars/ wrapper from src/ OK ✓