Bug #119585 In extreme cases, sync_array_get_and_reserve_cell fails to obtain a cell even have space
| Submitted: | 23 Dec 2025 3:17 | ||
|---|---|---|---|
| Reporter: | yuxiang jiang | Email Updates: | |
| Status: | Open | Impact on me: | |
| Category: | MySQL Server: InnoDB storage engine | Severity: | S3 (Non-critical) |
| Version: | 8.0.30/9.4.0/8.4.6 | OS: | Any |
| Assigned to: | CPU Architecture: | Any |
[23 Dec 2025 5:43]
Knut Anders Hatlen
Thank you for the bug report. The functions you refer to seem to be in the InnoDB code, so I'm changing the bug category to InnoDB.
[13 Jul 23:49]
Marcos Albe
Just a bit more detailed explanation: sync_array_get uses get_rnd_index, which is based on my_timer_cycles, which in turn is based on RDTSC instruction; In older CPU microarchitectures RDTSC would return values that increment by 1 (per-core/per-cycle), but in more modern CPUs it will return (for example) multiples of 32. This is a problem because when you use modulo on these values, they will split into a limited number of "buckets", which in the case of sync_array_get results in MUCH higher probability of hitting https://bugs.mysql.com/bug.php?id=119567 As I was doing a bit of research I found this interesting reference table for rdtsc increments in different AMD microarchitectures (quoting from https://inria.hal.science/hal-02866777v1/document): CPU μ-arch. Increment =================== =========================== ========= AMD Athlon 64 X2 3800+ K8 1 AMD Turion II Neo N40L K10 1 AMD Phenom II X6 1055T K10 1 AMD E-450 Bobcat 1 AMD Athlon 5350 Jaguar 1 AMD FX-4100 Bulldozer 1 AMD FX-8350 Piledriver 1 AMD A10-7870K Steamroller 1 AMD Ryzen Threadripper 1920X Zen 35 AMD Ryzen Threadripper 1950X Zen 34 AMD Ryzen Threadripper 1700X Zen 34 AMD Ryzen Threadripper 2970WX Zen+ 30 AMD Ryzen 7 3700X Zen 2 36 AMD EPYC 7401p Zen 20 AMD EPYC 7571 Zen 22 So you can see that the my_timer_cycles will be tied to the CPU architecture. To "repeat" (or rather to verify the problem is there): ======================================================== Set innodb_sync_array_size=1024 and run some simple by contentious load; For example: ~$ sysbench $auth --db-driver=mysql --time=0 --rand-type=uniform --table-size=2 --tables=1 --threads=512 --report-interval=5 --create_secondary=on /usr/share/sysbench/oltp_read_write.lua run; Then observe the sync arrays via first parameter to sync_array_reserve_cell: ~$ sudo bpftrace -e 'uprobe:/home/kolita/opt/percona/8.4.7/bin/mysqld:_Z23sync_array_reserve_cellP12sync_array_tPvmN2ut8LocationE { printf("arr: %u (%u)\n",arg0,arg0%32); }' Attaching 1 probe... arr: 3875288064 (0) arr: 3869525952 (0) arr: 3867788288 (0) arr: 3871258304 (0) arr: 3868232576 (0) arr: 3871685504 (0) arr: 3868664672 (0) arr: 3874429664 (0) arr: 3873562496 (0) arr: 3869959648 (0) arr: 3867789248 (0) arr: 3871254784 (0) arr: 3871255424 (0) arr: 3869962368 (0) arr: 3867363424 (0) arr: 3867364064 (0) arr: 3873561856 (0) arr: 3874431744 (0) Notice how modulo 32 always returns zero, which means only 32 of the 1024 sync array entries will be used. Fix (?): ======= Use a different source for get_rnd_index (https://github.com/mysql/mysql-server/blob/845d525d49c8027a4d0cdcc43372c96ba295c857/storag...) so that it doesn't depend on architecture implementations.
Portions of this website are copyright © 2001, 2002 The PHP Group
Page generated in 0.050 sec. using MySQL 8.4.7-u6-cloud
Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the
respective copyright holders.
It is not reviewed in advance
by Oracle and does not necessarily represent the opinion of
Oracle or any other party.
Description: In extreme cases, there may be free cells in the sync_wait_array, but the function sync_array_get_and_reserve_cell fails to acquire one, leading to an abnormal crash. The function sync_array_get_and_reserve_cell calls sync_array_get to obtain an array from the sync_wait_array. The following code attempts to acquire elements from the sync_wait_array through a loop. However, it cannot guarantee that all elements will be accessed within the loop—some may be accessed multiple times, and in extreme cases, the same element may be retrieved every time: for (ulint i = 0; i < sync_array_size && *cell == nullptr; ++i) { /* Although the sync_array is get in a random way currently, we still try at most sync_array_size times, in case any of the sync_array we get is full */ sync_arr = sync_array_get(); *cell = sync_array_reserve_cell(sync_arr, object, type, location); } If the cells of this particular element are exhausted, it will ultimately lead to a crash upon exiting the loop. How to repeat: Modify the following function so that default_indexer_t<>::get_rnd_index() always returns a fixed value to simulate the extreme cases. static inline sync_array_t *sync_array_get() { if (sync_array_size <= 1) { return (sync_wait_array[0]); } return ( sync_wait_array[default_indexer_t<>::get_rnd_index() % sync_array_size]); } Suggested fix: use a random positon and visit all the sync_wait_array element in a loop from this position