microsoft / microsoft/FluidFramework

Duplicate Code Detected: sendPending batch swap/checkpoint loop in Routerlicious lambdas

Open
#26,942 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
4.9k
Forks
586
Avg merge
1d 15h
Merged PRs (30d)
146

Description

🔍 Duplicate Code Detected: sendPending batch swap/checkpoint loop in Routerlicious lambdas

Analysis of commit 19a716b6250d945acb514205d6f452bd524b4a57

Assignee: @copilot

Summary

Multiple Routerlicious lambdas implement near-identical sendPending() logic: early return when work-in-progress or nothing pending, swap current/pending buffers, process all batches via Promise.all(...), checkpoint the batch offset, then recursively call sendPending(); errors trigger context.error(..., { restart: true }).

This is a classic copy/paste maintenance hotspot: small behavioral fixes (checkpoint conditions, retry/backoff, shutdown semantics, metric timing) must be duplicated across lambdas.

Duplication Details

Pattern: Pending-batch drain loop with buffer swap + Promise.all + checkpoint + recurse
  • Severity: Medium

  • Occurrences: 3 similar implementations

  • Locations:

    • server/routerlicious/packages/lambdas/src/moira/lambda.ts (lines 73–102)
    • server/routerlicious/packages/lambdas/src/copier/lambda.ts (lines 71–100)
    • server/routerlicious/packages/lambdas/src/scriptorium/lambda.ts (lines 217–330) — extended with telemetry, batching caps, and circuit-breaker pausing, but retains the same core structure
  • Code Sample (common structure, simplified):

    private sendPending(): void {
      // If there is work currently being sent or we have no pending work return early
      if (this.current.size > 0 || this.pending.size === 0) {
        return;
      }
    
      // Swap current and pending
      const temp = this.current;
      this.current = this.pending;
      this.pending = temp;
      const batchOffset = this.pendingOffset;
    
      const allProcessed: Promise(void)[] = [];
      for (const [, messages] of this.current) {
        allProcessed.push(this.processX(messages));
      }
    
      Promise.all(allProcessed)
        .then(() => {
          this.current.clear();
          this.context.checkpoint(batchOffset as IQueuedMessage);
          this.sendPending();
        })
        .catch((error) => {
          this.context.error(error, { restart: true });
        });
    }
    

Impact Analysis

  • Maintainability: Changes to batching strategy, checkpoint timing, shutdown behavior, and error/retry policy likely need to be applied consistently across lambdas, but are currently spread across multiple implementations.
  • Bug Risk: Divergence risk is high—future fixes may be applied to one lambda but missed in others, leading to inconsistent restart/checkpoint semantics.
  • Code Bloat: The pattern is relatively verbose and repeated; extracting the shared skeleton would reduce repeated control-flow boilerplate.

Refactoring Recommendations

  1. Extract a shared “pending batch drain” helper

    • Extract common control-flow into a utility, e.g. server/routerlicious/packages/lambdas/src/utils/pendingBatchDrain.ts.
    • Make it generic over:
      • data structure (Map(K, V) batches vs other queues)
      • processing function (sync/async)
      • checkpoint behavior (when/how to checkpoint)
      • error handling (restart vs pause)
    • Estimated effort: Medium (2–6 hours), depending on desired generality.
    • Benefits: centralized correctness + easier consistent instrumentation.
  2. Unify checkpoint + recursion semantics

    • Consider a single implementation that can optionally:
      • checkpoint only on success
      • checkpoint when pending becomes empty
      • schedule the next drain (immediate recursion vs setImmediate/task scheduling)
    • Estimated effort: Medium.

Implementation Checklist

  • Review duplication findings
  • Prioritize refactoring tasks
  • Create refactoring plan
  • Implement changes
  • Update tests
  • Verify no functionality broken

Analysis Metadata

  • Analyzed Files: 15 top-churn TS files + Routerlicious lambdas cross-check
  • Detection Method: Serena semantic code analysis (symbol-level extraction + cross-file comparison)
  • Commit: 19a716b6250d945acb514205d6f452bd524b4a57
  • Analysis Date: 2026-04-04T21:46:46.663Z

Generated by Duplicate Code Detector ·

To install this agentic workflow, run

gh aw add github/gh-aw/.github/workflows/duplicate-code-detector.md@94662b1dee8ce96c876ba9f33b3ab8be32de82a4

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Compare sendPending() in server/routerlicious/packages/lambdas/src/moira/lambda.ts, copier/lambda.ts, and scriptorium/lambda.ts, beginning with their shared buffer, batch, checkpoint, and error-handling flow. Define a shared helper without losing scriptorium's telemetry, batching caps, or circuit-breaker behavior, then update tests and verify that lambda functionality remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend, distributed-systems
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.