Back to Stackexchange Redis

SER306: waiting for a fire-and-forget result yields nothing

docs/rules/SER306.md

3.1.312.7 KB
Original Source

SER306: waiting for a fire-and-forget result yields nothing

CommandFlags.FireAndForget means "send this, and do not report back". The command is queued and sent like any other, but the task you get is already completed, carrying the default value - it is not, and never will be, the server's answer. Reading it is not an error, it is just always null/0/false.

c#
// flagged - value is always RedisValue.Null, whenever you read it
var tran = db.CreateTransaction();
var value = await tran.StringGetAsync(key, CommandFlags.FireAndForget);
await tran.ExecuteAsync();

// suggested - if you want the result, do not ask for fire-and-forget
var pending = tran.StringGetAsync(key);
await tran.ExecuteAsync();
var value = await pending;

// or, if fire-and-forget is what you meant, discard it
_ = tran.StringSetAsync(key, value, flags: CommandFlags.FireAndForget);
await tran.ExecuteAsync();

Unlike SER305 this does not hang, which is why it is a warning rather than an error, and why it has its own ID: deliberate fire-and-forget code needs to be able to silence this without silencing the rule that says a transaction cannot work.

The only fix offered is discard the queued result. Capturing the task and awaiting it after Execute - which is the fix for SER305 - gains nothing here, because the value does not improve with waiting.

Where it applies

Anywhere the flag is visible at compile time, on a transaction or a batch:

c#
tran.StringGetAsync(key, CommandFlags.FireAndForget)                             // flagged
tran.StringGetAsync(key, CommandFlags.FireAndForget | CommandFlags.DemandMaster) // flagged
tran.StringGetAsync(key, flags | CommandFlags.FireAndForget)                     // flagged - `|` only sets bits
tran.StringGetAsync(key, flags)                                                  // not flagged - unknowable

The third case works because | can only ever add the flag, whatever flags holds at runtime. There is no matching treatment of &/~ to prove the flag absent: that would feed SER305, which is an error, and an error must not rest on a partly-understood expression.

Cases that are deliberately not flagged

  • Fire-and-forget straight to the database. db.StringSetAsync(key, value, flags: FireAndForget) is ordinary code; this rule is only about waiting for a result you have declined.
  • A discarded result. _ = tran.StringSetAsync(...) is exactly what the rule asks for.

Suppressing

Reported as a warning, so TreatWarningsAsErrors builds fail until you act on it or turn it down.

xml
<NoWarn>$(NoWarn);SER306</NoWarn>

or locally:

c#
#pragma warning disable SER306

See also Transactions and SER305.