Skip to content

fix: Memcached decrement() gives the wrong sign on a missing key - #10512

Open
mdalikadar wants to merge 3 commits into
codeigniter4:developfrom
mdalikadar:fix/memcached-decrement-initial-value
Open

fix: Memcached decrement() gives the wrong sign on a missing key#10512
mdalikadar wants to merge 3 commits into
codeigniter4:developfrom
mdalikadar:fix/memcached-decrement-initial-value

Conversation

@mdalikadar

Copy link
Copy Markdown

Fixes #10510 (#10510)

Small follow-up to that issue: MemcachedHandler::decrement() was passing
$offset as the initial value for a key that doesn't exist yet, which made
a fresh key end up at +$offset instead of -$offset like every other
cache handler (File, Redis, Predis) gives you.

I swapped it to use 0 instead, since Memcached counters are unsigned and
can't actually go negative — so 0 is the closest sane starting point,
and it matches what Memcached::decrement() already defaults to on its
own.

What I changed:

  • MemcachedHandler::decrement() now passes 0 instead of $offset as
    the initial value
  • Updated the one test that was asserting the old (backwards) behavior
  • Added a changelog entry

@mergeable

mergeable Bot commented Aug 31, 2026

Copy link
Copy Markdown

Hi there, mdalikadar! 👋

Thank you for sending this PR!

We expect the following in all Pull Requests (PRs).

Important

We expect all code changes or bug-fixes to be accompanied by one or more tests added to our test suite to prove the code works.

If pull requests do not comply with the above, they will likely be closed. Since we are a team of volunteers, we don't have any more time to work
on the framework than you do. Please make it as painless for your contributions to be included as possible.

See https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md

Sincerely, the mergeable bot 🤖

MemcachedHandler::decrement() passed $offset as Memcached::decrement()'s
initial_value, which is used as-is (not decremented from) when a key
doesn't exist yet. That made a fresh key end up at +$offset instead of
the -$offset every other cache handler (File, Redis, Predis) produces.

Memcached counters are unsigned, so they can't hold a negative initial
value the way the other handlers effectively can. Use 0 instead (also
Memcached::decrement()'s own default), so a fresh key at least stops
going the wrong direction.

Updates the existing MemcachedHandlerTest::testDecrement() expectation
and adds a changelog entry.
Comment on lines +185 to +187
// Fall back to Memcached::decrement()'s own default of 0 instead
// of $offset, so a new key no longer starts positive.
return $this->memcached->decrement($key, $offset, 0, 60);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we reframe this as a Memcached-specific correction rather than a cross-handler consistency fix, and document the unsigned/saturating counter limitation in the user guide?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point, done. Reworded the comment so it's just about Memcached's own unsigned/saturating counters, no more comparing it to the other handlers. Also added a note in the user guide under decrement() explaining that missing keys start at 0 and existing counters can't go below 0 either.

- **Helpers:** Fixed a bug where ``get_dir_file_info()`` returned incomplete entries for subdirectories and missing files instead of omitting them.
- **Honeypot:** Fixed a bug where bot detection returned an HTTP 500 response instead of 403 (Forbidden).
- **Logger:** Fixed a bug where interpolating a log message with array or non-stringable context values could raise PHP warnings or errors.
- **Cache:** Fixed a bug where ``MemcachedHandler::decrement()`` on a non-existent key returned a positive value instead of ``0``, the opposite sign of what ``FileHandler``, ``RedisHandler``, and ``PredisHandler`` return.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This wording still suggests consistency with File, Redis, and Predis, but those handlers return -$offset, whereas this change returns 0. Could we describe the actual correction more directly?

- **Cache:** Fixed ``MemcachedHandler::decrement()`` initializing a non-existent counter to the positive offset. Missing counters are now initialized to ``0``, reflecting Memcached's unsigned, saturating counter semantics.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Makes sense, you are right that it was not quite accurate. Used your wording for the changelog entry.

Comment on lines +154 to +156
// A key that doesn't exist yet starts at 0, not at the offset
// (Memcached counters are unsigned, so it can't start negative).
$this->assertSame(0, $memcachedHandler->decrement(self::$key3, 1));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use an offset greater than 1 and also verify the stored value? That would make the regression clearer and prove that 0 is used as the initial value rather than merely matching a special case.

$this->assertSame(0, $memcachedHandler->decrement(self::$key3, 5));
$this->assertSame(0, $memcachedHandler->get(self::$key3));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, offset of 1 did not really prove anything. Changed it to 5 and added a get() check on the stored value like you suggested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: MemcachedHandler::decrement() produces the opposite sign of File/Redis/Predis for a non-existent key

2 participants