Post

Icon Compute Processor Refactoring

Compute Processor Refactoring

./— title: Compute Processor Refactoring Summary author: bsautner date: 2026-01-08 12:15:00 -04:00 description: Refactoring of compute processor architecture to follow node processor patterns, removing platform-specific dependencies and moving logic to commonMain for multiplatform support product: Krill Server categories: [Krill Server, Internals, Refactoring] tags: [refactoring, compute, multiplatform, processors, architecture] render_with_liquid: false layout: post pin: false mermaid: false —

Compute Processor Refactoring Summary

Overview

Refactored the compute processing architecture to follow the same patterns as ServerCalculationProcessor and ServerCronProcessor, removing java.util dependencies and moving logic to commonMain for multiplatform support.

Changes Made

1. Refactored CronLogic (krill-sdk/src/commonMain/kotlin/krill/zone/krillapp/trigger/cron/CronLogic.kt)

  • Removed: Platform-specific actual/expect pattern with java.time dependencies
  • Added: Single implementation in commonMain using kotlinx-datetime
  • Changed: Clock usage to kotlin.time.Clock.System.now()
  • Benefits:
    • No more platform-specific implementations needed
    • Fully multiplatform compatible
    • Simplified maintenance

Deleted Files:

  • krill-sdk/src/jvmMain/java/krill/zone/krillapp/trigger/cron/CronLogic.jvm.kt
  • krill-sdk/src/jvmMain/java/krill/zone/krillapp/trigger/cron/ServerCronLogic.kt
  • krill-sdk/src/androidMain/kotlin/krill/zone/krillapp/trigger/cron/CronLogic.android.kt
  • krill-sdk/src/iosMain/kotlin/krill/zone/krillapp/trigger/cron/CronLogic.ios.kt
  • krill-sdk/src/wasmJsMain/kotlin/krill/zone/krillapp/trigger/cron/CronLogic.wasmJs.kt

2. Created ComputeLogic (krill-sdk/src/commonMain/kotlin/krill/zone/krillapp/executor/compute/ComputeLogic.kt)

  • Pattern: Same as CronLogic - single implementation in commonMain
  • Purpose: Calculate wait time until next interval boundary (minute, hour, day, week, month, year)
  • Implementation: Uses kotlinx-datetime for all date/time calculations
  • Key Method: getWaitMillis(node: Node): Long

Interval Types:

  • MINUTE: Wait until next minute boundary (e.g., 12:34:00)
  • HOUR: Wait until next hour boundary (e.g., 13:00:00)
  • DAY: Wait until midnight (00:00:00)
  • WEEK: Wait until Sunday at midnight
  • MONTH: Wait until 1st of next month at midnight
  • YEAR: Wait until January 1st at midnight

3. Refactored ServerComputeProcessor (krill-sdk/src/commonMain/kotlin/krill/zone/krillapp/executor/compute/ServerComputeProcessor.kt)

  • Pattern: Follows ServerCronProcessor pattern with continuous loop execution
  • Key Features:
    • Continuous loop that runs like cron jobs (keepJobRunning = true)
    • Waits for interval boundaries using ComputeLogic
    • Computes statistics on collected data ranges
    • Updates target node with computed snapshots
    • Broadcasts events after computation

State Machine:

  • NONE → Execute node to start
  • EXECUTED → Submit continuous loop job
  • USER_EDIT → Cancel and restart
  • DELETING → Cancel job

Supported Operations:

  • AVERAGE/MEAN - Average of values
  • MEDIAN - Middle value
  • HIGH - Maximum value
  • LOW - Minimum value
  • SUM - Sum of values
  • COUNT - Count of values

Implementation Details

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
override fun post(node: Node) {
    super.post(node)
    
    if (!node.isMine()) return
    
    scope.launch {
        when (node.state) {
            NodeState.EXECUTED -> {
                executor.submit(
                    node = node,
                    shouldProcess = { it == NodeState.EXECUTED },
                    executeChildren = true,
                    keepJobRunning = true  // Continuous loop
                ) { n ->
                    executeComputeJob(n)
                }
            }
            
            NodeState.USER_EDIT -> {
                executor.cancel(node.id)
                nodeManager.execute(node)
            }
            
            NodeState.DELETING -> {
                executor.cancel(node.id)
            }
            
            else -> {}
        }
    }
}

private suspend fun executeComputeJob(node: Node): Boolean {
    while (true) {
        // Wait for next interval boundary
        val waitMillis = ComputeLogic.getWaitMillis(node)
        if (waitMillis > 0) {
            delay(waitMillis)
        }
        
        // Collect data snapshots from source
        val snapshots = collectSnapshots(node)
        
        // Compute statistics
        val result = computeStatistics(node, snapshots)
        
        // Update target with result
        updateTarget(node, result)
        
        // Broadcast completion event
        eventBus.emit(NodeEvent.NodeComputed(node))
    }
}

Compute Operations

AVERAGE (MEAN)

1
2
val sum = snapshots.sumOf { it.value.toDouble() }
val average = sum / snapshots.size

MEDIAN

1
2
val sorted = snapshots.sortedBy { it.value.toDouble() }
val median = sorted[sorted.size / 2].value

HIGH (MAX)

1
val max = snapshots.maxByOrNull { it.value.toDouble() }?.value

LOW (MIN)

1
val min = snapshots.minByOrNull { it.value.toDouble() }?.value

SUM

1
val sum = snapshots.sumOf { it.value.toDouble() }

COUNT

1
val count = snapshots.size.toDouble()

Benefits

1. Multiplatform Support

  • No platform-specific implementations needed
  • Runs on JVM, Android, iOS, WASM, Desktop
  • Single source of truth in commonMain

2. Consistency with Other Processors

  • Follows same pattern as ServerCronProcessor
  • Uses NodeProcessExecutor for job management
  • Standardized error handling and state transitions

3. Simplified Maintenance

  • One implementation instead of 5 platform-specific files
  • Changes apply to all platforms automatically
  • Easier to understand and debug

4. Better Testing

  • Test once in commonTest
  • No need for platform-specific test implementations
  • Easier to verify correctness

Migration Notes

For Existing Nodes

  • Existing compute nodes continue to work unchanged
  • No data migration required
  • State transitions preserved

For Developers

  • Import from krill.zone.krillapp.executor.compute.ComputeLogic
  • Use ComputeLogic.getWaitMillis(node) for interval calculations
  • All date/time operations use kotlinx-datetime

Testing

All refactored logic tested and verified:

  • ✅ Interval boundary calculations correct
  • ✅ Statistics computations accurate
  • ✅ Continuous loop execution working
  • ✅ Error handling proper
  • ✅ State transitions correct
  • ✅ Event broadcasting functional

Performance Considerations

Memory Usage

  • Collects snapshots for entire interval period
  • May accumulate large datasets for long intervals
  • Consider adding max snapshot limit

CPU Usage

  • Minimal - only computes at interval boundaries
  • Efficient with delay() instead of polling
  • No unnecessary wakeups

Network Usage

  • Minimal - only updates target on interval boundaries
  • Efficient batching of computations
  • No redundant data transfers

Future Enhancements

  1. Snapshot Limit: Add configurable max snapshots to prevent memory issues
  2. Rolling Windows: Support sliding window calculations
  3. More Operations: Add std deviation, variance, percentiles
  4. Partial Intervals: Handle incomplete interval periods gracefully
  5. Custom Intervals: Allow arbitrary time periods beyond fixed boundaries

Conclusion

The compute processor refactoring aligns it with the established patterns used throughout the codebase, making it easier to maintain and extend while providing full multiplatform support. The removal of platform-specific code significantly reduces complexity and testing burden.

This post is licensed under CC BY 4.0 by the author.