Skip to content

fix: stop leaking one orphaned session file per request in the Pixel buffer - #609

Open
tiacocstoe wants to merge 1 commit into
PrestaShopCorp:masterfrom
tiacocstoe:fix/pixel-buffer-orphaned-session-files
Open

tiacocstoe wants to merge 1 commit into
PrestaShopCorp:masterfrom
tiacocstoe:fix/pixel-buffer-orphaned-session-files

Conversation

@tiacocstoe

Copy link
Copy Markdown

Issue: PrestaShop/PrestaShop#42389

What this PR does

TemplateBuffer::init() always builds a MockFileSessionStorage, even when it is handed an empty
$userId. In that case MockArraySessionStorage::start() generates a random session id:

if (empty($this->id)) {
    $this->id = $this->generateId();   // hash('sha256', uniqid('ss_mock_', true))
}

The file written at shutdown is therefore keyed by an id that no later request will ever produce
again
. It can never be read back, it is never reused, and MockFileSessionStorage implements no
garbage collection, so it stays on disk permanently.

This PR keeps file backed storage for the case it was designed for, a stable identifier, and falls
back to in memory storage otherwise.

Why $userId is empty for most traffic

PixelHandler::findIdentifierFromContext():

if (!empty($context->customer->id_guest)) {
    return 'guest_' . $context->customer->id_guest;
}
if (!empty($context->cart->id)) {
    return 'cart_' . $context->cart->id;
}

return '';

Customer::$id_guest is a public property that is not declared in
Customer::$definition['fields']
, so ObjectModel never hydrates it. PrestaShop keeps the guest
id on the cookie (Guest::setNewGuest() writes $cookie->id_guest), not on the customer object.
For an anonymous visitor the first branch can never match, and the second only matches visitors who
already have a cart.

Filename distribution measured on a production shop confirms it:

18 085  <sha256>.mocksess      random, orphaned on creation
    58  cart_<id>.mocksess     stable, reused correctly
     0  guest_<id>.mocksess    never produced

Impact

Measured on a medium traffic PrestaShop 1.7.6.8 shop running ps_facebook 1.38.17 with an active
Pixel. The directory was counted three times in one morning:

10:04   17 871 files
10:10   17 993 files
10:18   18 143 files

272 files in 14 minutes, roughly 28 000 files per day. The shop reached its hosting limit of
5 000 000 inodes; clearing var/cache released about 3 460 000 inodes, some four months of
accumulation.

The failure mode is quiet: the files are around 200 bytes each, so 18 000 of them take 3.5 MB and no
disk usage alert ever fires. Only inode counting reveals it. And because the directory sits inside
var/cache, any routine cache clear resets the counter, which is presumably why this has gone
unnoticed since v1.24.0.

Behaviour change

None that is observable. Requests without a stable identifier already could not restore their buffer
on a subsequent request, because the id was random every time. This PR stops writing a file that
nobody could ever read. Requests carrying cart_<id> keep their current cross page behaviour.

Not covered by this PR

  • findIdentifierFromContext() reading $context->customer->id_guest is dead code and should
    probably read $context->cookie->id_guest instead. I deliberately left it out: it would not
    fix this leak, because Cookie::makeNewLog() calls Guest::setNewGuest() on every cookieless
    request, so crawlers would simply generate guest_<id> files at the same rate. It deserves its
    own change.
  • The cart_<id> files are also never cleaned up once the cart is gone. A gc() pass or a TTL on
    the directory would bound that too.
  • TemplateBuffer::clean() discards the return value of getFlashBag()->get() and is never called
    anywhere in the module.

Regression origin

Introduced by 0f3a36d,
2023-01-12, "Improve Pixel buffer to keep events between different pages", which replaced the
previous in memory string buffer with file backed session storage.

First release containing it: v1.24.0-beta.1 (2023-01-31), stable v1.24.0 (2023-02-06). Neither
TemplateBuffer.php nor PixelHandler.php has been touched since, so every release up to the
current v1.38.19 is affected.

How to test

  1. Install and configure the module with a valid Pixel id.
  2. ls var/cache/prod/ps_facebook_sessions | wc -l
  3. Issue a few cookieless front office requests, for example
    for i in $(seq 1 20); do curl -s https://shop.example/ -o /dev/null; done
  4. Count again.

Before this PR the count grows by one per request. After it, the count stays flat while
cart_<id> files continue to be written and reused for visitors with a cart.

@tiacocstoe
tiacocstoe requested a review from a team as a code owner August 20, 2026 11:16
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.

1 participant