---
title: Stop a timed operation
summary: Finish a previously started timer and record the elapsed span, counters, and range statistics.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Framework.CodeTimer
reference_type: package
operation_id: CT-OP-002
trigger: Caller invokes CodeTimer.Stop(MethodBase...) for a timer key that was started earlier.
---

# Stop a timed operation

This operation closes a running timer entry, rolls the interval that just elapsed into the entry's cumulative statistics, and returns the updated record to the caller. It mirrors the name and hierarchy resolution performed by the start operation so that a `Stop` call always lands on the exact same dictionary entry its matching `Start` call created or resumed.

| Field | Value |
| --- | --- |
| **Operation ID** | CT-OP-002 |
| **Trigger** | Caller invokes `CodeTimer.Stop(MethodBase...)` |
| **Inputs** | `MethodBase method`, optional `customName`, optional `parentTimerKey` |
| **Outputs** | Updated `CodeTimerEntry`, or `null` when the stop call is gated off |
| **Primary components** | `CodeTimer.Stop`, `CodeTimer.GetCodeTimerParent`, `CodeTimerEntry.Stop`, `CustomTimeSpan.CustomTimeSpan` |
| **Entry point** | `SAIF.Framework.CodeTimer\SAIF.Framework.CodeTimer\CodeTimer.cs` |

## Steps

1. Rebuild the method timer name using the same rule the start path uses, including the optional `.{customName}` suffix, so the name computed here is guaranteed to match the name computed at start time.
2. Inspect the target method for `CodeTimerAttribute`, the same per-method gate the start path checks.
   - If no attribute is present, return `null` immediately and leave the timer store untouched ([BR-005](../business-rules.md#br-005-timing-is-only-recorded-when-both-the-global-switch-and-any-per-method-attribute-allow-it)).
   - If the attribute exists but its `Enabled` flag is `false`, also return `null` without touching timer state ([BR-005](../business-rules.md#br-005-timing-is-only-recorded-when-both-the-global-switch-and-any-per-method-attribute-allow-it)).
3. Rebuild the same hierarchical key the corresponding start call produced, so the stop request addresses the correct dictionary entry rather than a namesake at a different nesting level.
4. Evaluate the global `CodeTimer.Enabled` switch.
   - If the switch is `false`, return `null` without updating any timer entry, mirroring the start path's behavior when timing is globally suppressed ([BR-005](../business-rules.md#br-005-timing-is-only-recorded-when-both-the-global-switch-and-any-per-method-attribute-allow-it)).
5. Require the composite key to already exist in the static `data` dictionary.
   - On failure: if the key is missing — for example, `Stop` was called without a matching prior `Start` — throw the missing-key exception defined by [BR-004](../business-rules.md#br-004-stopping-a-timer-by-key-requires-the-key-to-already-exist) rather than silently creating an entry.
6. Call `CodeTimerEntry.Stop(true)` on the matching entry to close out the current run.
   - On failure: if the entry is not currently marked running — for example, `Stop` was called twice in a row for the same key — throw under [BR-002](../business-rules.md#br-002-a-timer-must-not-be-stopped-while-not-running).
   - On success: stamp `lastStopped` with the current time, compute `diff = lastStopped - lastStarted`, add that duration into the entry's cumulative `elapsed` total, append a new `CustomTimeSpan` capturing this individual run, update `longest` and `shortest` if the new span exceeds or undercuts the prior extremes, and clear the entry's running flag so a subsequent `Start` can resume it.
7. Return the updated `CodeTimerEntry` to the caller, now reflecting the just-completed interval in its counters and span history.

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
	Start([Stop request]) --> Resolve[Resolve method and custom name]
	Resolve --> Attr{Attribute present<br>and enabled?}
	Attr -->|No| NoOp([Return null])
	Attr -->|Yes| BuildKey[Build hierarchical key]
	BuildKey --> Enabled{CodeTimer.Enabled?}
	Enabled -->|No| Disabled([Return null])
	Enabled -->|Yes| Exists{Key exists?}
	Exists -->|No| Missing([Throw missing-key exception])
	Exists -->|Yes| Running{Entry running?}
	Running -->|No| NotRunning([Throw not-running exception])
	Running -->|Yes| Update[Record span and totals]
	Update --> EndOk([Return CodeTimerEntry])
```

## Failure and edge-case behavior

- A method with no `CodeTimerAttribute` produces no stop action at all and simply returns `null`, the same "opt-out" behavior as the start path.
- Stopping a key that was never started — or was already reset — throws under [BR-004](../business-rules.md#br-004-stopping-a-timer-by-key-requires-the-key-to-already-exist) instead of returning `null` or creating a placeholder entry.
- Stopping an entry that is not currently running, such as a duplicate stop call, throws under [BR-002](../business-rules.md#br-002-a-timer-must-not-be-stopped-while-not-running) rather than being treated as a harmless no-op.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)
- [Configuration](../configuration.md)
- [API reference](../api/index.md)

## Sources
- SAIF.Framework.CodeTimer\SAIF.Framework.CodeTimer\CodeTimer.cs
- SAIF.Framework.CodeTimer\SAIF.Framework.CodeTimer\CodeTimerAttribute.cs
- SAIF.Framework.CodeTimer\SAIF.Framework.CodeTimer\CodeTimerEntry.cs
- SAIF.Framework.CodeTimer\SAIF.Framework.CodeTimer\CustomTimeSpan.cs
