Tables: a number filter in scientific notation with a huge exponent holds a worker for minutes #499
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#499
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the table filters and timing them with the snapshot's interpreter.Checked again on 2026-10-01 against
mainat0a5cad587: still present.A table's min/max filter accepts any finite
Decimal, including one written as a short exponent. The bound is then handed to a lookup on an integer count, and Django turns it into a Pythonint, which for a value like1e1000000takes tens of seconds and grows with the square of the exponent. One GET of a few characters ties up a gunicorn worker until it is killed.core/tables.py:729-735,_number, whose only check isvalue.is_finite();core/tables.py:582-588,Table.filter, which passes it to{lookup}__gte/__lte; the number columns injobs/tables.py:68(posting_count),:78(application_count),:105(contact_count) and:287(listings'application_count).postings_min(or any*_min/*_maxof a number column) arrives fromrequest.GETthroughTable.given,_numberparses it asDecimal, and the integer field'sget_prep_valuecallsint()on it. Measured:int(Decimal("1e100000"))takes 0.35 s,1e3000003.2 s, and1e1000000about 35 s; the time is all in that conversion. A nine-character value runs past the image's--timeout 120(docker/Dockerfile:354), and the image runs three sync workers (docker/Dockerfile:362)._numbercan accept only an optional sign and at most, say, 12 digits (re.fullmatch(r"[+-]?\d{1,12}", text)) and return anint; if decimals are wanted later, refuse aDecimalwhoseadjusted()exceeds 18. What is refused narrows nothing, as an unparsable value does today.Done when
tests/test_tables.pyhas a test that?postings_min=1e1000000on the companies table answers within a second and leaves the rows unfiltered, and one that?postings_min=3still narrows.