Skip to content
129 changes: 129 additions & 0 deletions docs/DeveloperResources/ErrorHandling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
---
# Error Handling

This page details
[SquidCodingGuidelines](/DeveloperResources/SquidCodingGuidelines) related to
validating code invariants and input.


## Primary API choices

There are several primary ways to handle error conditions in Squid code. For
any given context, only one approach is usually the correct choice. Using the
list below, pick the _first_ one that matches your use case. See further below
for notes about such special rare cases as bug workarounds, unreachable code,
optional custom assertion messages, and legacy code.

1. If the condition can be checked at compilation time, use `static_assert()`.
Minor code adjustments to make compile-time assertions possible may be
allowed, but Squid currently avoids explicit `constexpr`, and sprinkling
Squid code with many `constexpr` specifiers to get some compile-time
assertion working is usually a bad idea.
Comment on lines +20 to +22

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"avoids" implies that we have some sort of agreement not to use that construct. Reality is that no Squid contributor has considered it worth converting any of the code to using it.

I suggest leaving it at: (no more than) minor adjustments being acceptable.

Suggested change
allowed, but Squid currently avoids explicit `constexpr`, and sprinkling
Squid code with many `constexpr` specifiers to get some compile-time
assertion working is usually a bad idea.
allowed, but these should not extend nor exceed the minimal scope of logic change.


2. If the condition describes a code invariant (e.g., "our caller must supply
a non-nil pointer"), use `Assure()`. In most cases, `Assure()` failures
kill the checking transaction but keep its kid process alive. Neither
outcome is guaranteed though because Squid may catch and handle the
exception before it kills the transaction, or the exception may propagate
to the top level where it kills the kid process.

3. If the condition describes some input characteristics (e.g., "the client
sent a syntactically valid HTTP request to Squid"), do not use any of the
above calls. Instead, create and throw a `TextException` object, return
Comment on lines +32 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"if X, do not use any of the above" - directly contradicts the initial requirement "pick the first one that matches your use case".

This whole clause/condition is not clearly distinguished from the prior "code invariant" clause. Likely because this item is about transactional / protocol flow behaviour; whereas the prior clauses are about catching code logic errors.

`std::nullopt`, or otherwise signal the problem to the caller. In most
cases, adding a level-0/1 `debugs()` `ERROR` or `WARNING` message is _not_
a good idea. This is especially true when Squid cache administrator can do
nothing about that bad input, and that bad input does not represent some
very unusual or dangerous situation. Most transaction-related input
validation failures ought to be reflected in various error details logged
to `access.log`, not level-0/1 `cache.log` messages.


## Squid bug workarounds

In special rare cases, implementing a temporary bug workaround would be much
better than killing the affected transaction or Squid. In such cases, produce
`DBG_CRITICAL` or `DBG_IMPORTANT` _reporting_ with `ERROR` _and_ `Squid BUG`
labels but without calling `Assure()`.

```C++
debugs(33, DBG_IMPORTANT, "ERROR: Squid BUG: ConnStateData did not close " << clientConnection);
```

More good examples can be found among `git grep ERROR:.Squid.BUG:` matches.


## Unreachable code

Unreachable code is special because there is no meaningful condition to be
evaluated inside that code (unless you consider `true` to be meaningful).
Reaching an unreachable code is a Squid bug. If this bug can be detected at
compile time, use `#error` preprocessor instruction. Otherwise, use `Assure()`
with the following always-false condition pattern:

```C++
Assure(!"invariant description");
```

Good examples can be found among `git grep 'Assure.!"'` matches.


## Custom assertion messages

Compiler-generated `static_assert()`, `Assure()`, `assert()`, and deprecated
`Must()` error messages spell out the specified condition. In special rare
cases, it is desirable to replace that generated message with a custom one.
When doing so, please preserve the message generation algorithm by describing
what should be happening (i.e. the expected condition) rather than what went
wrong. For example,

```C++
// XXX: This bad custom message describes the problem rather than the condition:
static_assert(sizeof(quotedOut) > 0, "quotedOut has zero length");

// OK: These custom messages describe the condition:
static_assert(id > 0, "debugs() message ID must be positive");
Assure2(headerSize >= SwapMetaPrefixSize, "UnpackPrefix() validates metadata length");
```


## Legacy error handling

Legacy error handling macros include `Must()`, `Must3()`, and `TexcHere()`. Do
not use them in new code. The information below is provided to guide the
replacement of legacy calls with their modern equivalents.

* Legacy `Must()` was probably meant for checking input, but developers
started to use it for checking code invariants as well. The mixture of these
two use case categories made it impossible to address various `Must()`
problems, necessitating the introduction of `Assure()` and deprecation of
`Must()` in 2022 commit b9a1bbfb. When replacing `Must()`, one has to
determine the correct use case category (as detailed in the enumerated list
at the beginning of the "Error handling" section).

* Legacy `Must3()` is like `Must()` but supports a custom message. Existing
`Must3()` custom messages are inconsistent -- some describe the correct
condition and some describe the failure.

* Legacy `TexcHere()` is a convenience macro. Modern code spells out
`TextException` and `Here()` explicitly.

Keep in mind that it is usually best to leave legacy code intact. Upgrading

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep in mind that it is usually best to leave legacy code intact.

Hmm, this statement implies that you have seriously misunderstood the Best Practice principle of leaving old code alone. The purpose of which is actually to prevent young/new developers a) unnecessarily introducing bugs with code change, b) removing necessary bug workarounds while cleaning "messy" legacy code, or c) re-re-upgrading the same code to "latest new thing" in a loop instead of fixing underlying problems.
The practice only holds true when a) the developer is ignorant of the old code purpose and issues within, or b) the replacement "latest thing" is not yet proven to be better code.

The Squid code has reached the pace where almost all code is now outdated "legacy" patched together by layers of technical debt (eg wrapper macros and functions). Leaving the technical debt intact has long since stopped being a good thing.

legacy code may be appropriate when your pull request has to modify that
specific legacy code lines for other legitimate in-scope reasons or your pull
request is actually dedicated to upgrading legacy code. In those exceptional
cases, the author becomes responsible for providing a high quality
replacement, of course.
Comment on lines +115 to +117

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
request is actually dedicated to upgrading legacy code. In those exceptional
cases, the author becomes responsible for providing a high quality
replacement, of course.
request is actually dedicated to upgrading legacy code.

Code quality is subjective and your definition has proven to be both controversial and problematic. Do not make it a requirement for Squid contributors to meet that impossible bar. Remember that we have the officially core/board agreed policy that "good enough" is permissible - "high quality" is an Alex review thing, not a Squid code requirement.



## Other special cases

`Assure()` is not available in code residing outside of `src/`, in helper code
that has not been upgraded to use `src/base` APIs, and in legacy C code. Use

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
that has not been upgraded to use `src/base` APIs, and in legacy C code. Use
that has not been upgraded to use `src/base` APIs, and in legacy C-style callbacks. Use

The only actual "legacy C code" is outside src/ now, so strictly speaking that would be redundant text. However I suspect you actually mean the legacy code in src/ which is using the old C-style mechanisms. Of which the most notable are callbacks. If there are others, it might be worth enumerating theme here and also as items to fix in the contributor TODO list.

`assert()` instead.

The following error handling functions are not covered by this documentation.
They should be avoided in most cases, especially in new code: `fatalf()`,
`fatal()`, `fatal_dump()`, `xassert()`.
Comment on lines +126 to +128

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the same paradigm as the Must() etc, items you put in "Legacy error handling". Please either
Either;

  • document all of the mechanisms currently existing (even deprecated ones), or
  • omit the legacy/deprecated items entirely (proposed text about them in the SquidCodingGuidelines page is sufficient).

The author should exclusively use whatever of the mechanisms documented on this page are appropriate for their new/changed code.


13 changes: 13 additions & 0 deletions docs/DeveloperResources/SquidCodingGuidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ formater.
* Naming conventions as covered in
[Features/SourceLayout](/Features/SourceLayout)
are to be used.
* To assert a condition that can be checked at compilation time, use
`static_assert()`. See [ErrorHandling](/DeveloperResources/ErrorHandling)
for details.
* To confirm a code invariant that cannot be checked at compilation time, use
`Assure()` if you can and `assert()` otherwise. See
[ErrorHandling](/DeveloperResources/ErrorHandling) for details.
* Do not use `Assure()` or `assert()` to validate input. Instead, create and
throw a `TextException` object, return `std::nullopt`, or otherwise signal
the problem to the caller. See
[ErrorHandling](/DeveloperResources/ErrorHandling) for details.
* Do not use deprecated `Must()`, `Must3()`, fatalf()`, `fatal()`, and
`fatal_dump()` in new code. See
Comment on lines +70 to +71

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing xassert() and TexcHere()

[ErrorHandling](/DeveloperResources/ErrorHandling) for details.

### Rule: No new globals

Expand Down