Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/actions/apt-x32/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ runs:
OPCACHE_TLS_TESTS_DEPS="gcc clang lld"

export DEBIAN_FRONTEND=noninteractive
dpkg --add-architecture i386
apt-get update -y | true
sudo dpkg --add-architecture i386
sudo env DEBIAN_FRONTEND=noninteractive apt-get update -y | true
# TODO: Reenable postgresql + postgresql-contrib packages once they work again.
apt-get install -y \
sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y \
autoconf \
bison \
curl \
Expand Down
16 changes: 8 additions & 8 deletions .github/actions/install-linux-x32/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ runs:
- shell: bash
run: |
set -x
make install
mkdir -p /etc/php.d
chmod 777 /etc/php.d
echo mysqli.default_socket=/var/run/mysqld/mysqld.sock > /etc/php.d/mysqli.ini
echo pdo_mysql.default_socket=/var/run/mysqld/mysqld.sock > /etc/php.d/pdo_mysql.ini
echo opcache.enable_cli=1 >> /etc/php.d/opcache.ini
echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini
echo opcache.preload_user=root >> /etc/php.d/opcache.ini
sudo make install
sudo mkdir -p /etc/php.d
sudo chmod 777 /etc/php.d
echo mysqli.default_socket=/var/run/mysqld/mysqld.sock | sudo tee /etc/php.d/mysqli.ini > /dev/null
echo pdo_mysql.default_socket=/var/run/mysqld/mysqld.sock | sudo tee /etc/php.d/pdo_mysql.ini > /dev/null
echo opcache.enable_cli=1 | sudo tee /etc/php.d/opcache.ini > /dev/null
echo opcache.protect_memory=1 | sudo tee -a /etc/php.d/opcache.ini > /dev/null
echo opcache.preload_user=root | sudo tee -a /etc/php.d/opcache.ini > /dev/null
13 changes: 6 additions & 7 deletions .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,14 @@ jobs:
fail-fast: false
matrix: ${{ fromJson(inputs.branch).jobs.LINUX_X32.matrix }}
name: "LINUX_X32_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}"
runs-on: ubuntu-latest
# The container seccomp profile blocks i386 socket syscalls used by the test suite.
runs-on: ubuntu-${{ fromJson(inputs.branch).config.ubuntu_version }}
timeout-minutes: 180
container:
image: ubuntu:${{ fromJson(inputs.branch).config.ubuntu_version }}
env:
MYSQL_TEST_HOST: mysql
PDO_MYSQL_TEST_DSN: mysql:host=mysql;dbname=test
PDO_MYSQL_TEST_HOST: mysql
PDO_FIREBIRD_TEST_DSN: firebird:dbname=firebird:test.fdb
MYSQL_TEST_HOST: 127.0.0.1
PDO_MYSQL_TEST_DSN: mysql:host=127.0.0.1;dbname=test
PDO_MYSQL_TEST_HOST: 127.0.0.1
PDO_FIREBIRD_TEST_DSN: firebird:dbname=localhost:test.fdb
services:
mysql:
image: mysql:8.4
Expand Down
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ PHP NEWS
?? ??? ????, PHP 8.4.28

- Core:
. Garbage collection can complete wide graph traversals after the request
memory limit has been reached. (Alessio Attilio)
. Fixed bug GH-24184 (System ID not being reinitialized after an Apache
graceful restart, potentially causing incompatible OPcache file cache
entries to be reused). (Levi Morrison)
Expand Down
23 changes: 23 additions & 0 deletions Zend/tests/gh23586_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
GH-23586 (GC can finish a wide traversal at the memory limit)
--SKIPIF--
<?php
if (getenv('USE_ZEND_ALLOC') === '0') {
die('skip Zend MM required');
}
?>
--INI--
memory_limit=256M
--FILE--
<?php
gc_disable();
$root = new stdClass();
for ($i = 0; $i < 350000; $i++) {
$root->children[] = (object) ['parent' => $root];
}
unset($root);
ini_set('memory_limit', (string) memory_get_usage(true));
var_dump(gc_collect_cycles());
?>
--EXPECT--
int(350002)
30 changes: 30 additions & 0 deletions Zend/tests/gh23586_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
GH-23586 (GC traversal storage is released when a destructor bails out)
--SKIPIF--
<?php
if (getenv('USE_ZEND_ALLOC') === '0') {
die('skip Zend MM required');
}
?>
--INI--
error_reporting=E_ALL & ~E_DEPRECATED
--FILE--
<?php
class Node {
public $children = [];

public function __destruct() {
trigger_error('Destructor bailout', E_USER_ERROR);
}
}

gc_disable();
$root = new Node();
for ($i = 0; $i < 2000; $i++) {
$root->children[] = (object) ['parent' => $root];
}
unset($root);
gc_collect_cycles();
?>
--EXPECTF--
Fatal error: Destructor bailout in %s on line %d
48 changes: 34 additions & 14 deletions Zend/zend_gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,8 @@ struct _gc_stack {
static zend_never_inline gc_stack* gc_stack_next(gc_stack *stack)
{
if (UNEXPECTED(!stack->next)) {
gc_stack *segment = emalloc(sizeof(gc_stack));
/* Traversal must not bail out on the request memory limit after changing refcounts. */
gc_stack *segment = pemalloc(sizeof(gc_stack), true);
segment->prev = stack;
segment->next = NULL;
stack->next = segment;
Expand Down Expand Up @@ -371,7 +372,7 @@ static void gc_stack_free(gc_stack *stack)

while (p) {
stack = p->next;
efree(p);
pefree(p, true);
p = stack;
}
}
Expand Down Expand Up @@ -1943,7 +1944,7 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
}
}

ZEND_API int zend_gc_collect_cycles(void)
static int zend_gc_collect_cycles_impl(gc_stack *stack)
{
int total_count = 0;
bool should_rerun_gc = 0;
Expand All @@ -1961,10 +1962,6 @@ ZEND_API int zend_gc_collect_cycles(void)
zend_refcounted *p;
uint32_t gc_flags = 0;
uint32_t idx, end;
gc_stack stack;

stack.prev = NULL;
stack.next = NULL;

if (GC_G(gc_active)) {
GC_G(collector_time) += zend_hrtime() - start_time;
Expand All @@ -1976,17 +1973,16 @@ ZEND_API int zend_gc_collect_cycles(void)
GC_G(gc_active) = 1;

GC_TRACE("Marking roots");
gc_mark_roots(&stack);
gc_mark_roots(stack);
GC_TRACE("Scanning roots");
gc_scan_roots(&stack);
gc_scan_roots(stack);

GC_TRACE("Collecting roots");
count = gc_collect_roots(&gc_flags, &stack);
count = gc_collect_roots(&gc_flags, stack);

if (!GC_G(num_roots)) {
/* nothing to free */
GC_TRACE("Nothing to free");
gc_stack_free(&stack);
GC_G(gc_active) = 0;
goto finish;
}
Expand Down Expand Up @@ -2035,7 +2031,7 @@ ZEND_API int zend_gc_collect_cycles(void)
while (idx != end) {
if (GC_IS_DTOR_GARBAGE(current->ref)) {
p = GC_GET_PTR(current->ref);
count -= gc_remove_nested_data_from_buffer(p, current, &stack);
count -= gc_remove_nested_data_from_buffer(p, current, stack);
}
current++;
idx++;
Expand All @@ -2058,8 +2054,6 @@ ZEND_API int zend_gc_collect_cycles(void)
}
}

gc_stack_free(&stack);

/* Destroy zvals. The root buffer may be reallocated. */
GC_TRACE("Destroying zvals");
zend_hrtime_t free_start_time = zend_hrtime();
Expand Down Expand Up @@ -2144,6 +2138,32 @@ ZEND_API int zend_gc_collect_cycles(void)
return total_count;
}

static int zend_gc_collect_cycles_with_stack(gc_stack *stack)
{
int count;

zend_try {
count = zend_gc_collect_cycles_impl(stack);
} zend_catch {
gc_stack_free(stack);
zend_bailout();
} zend_end_try();

gc_stack_free(stack);
return count;
}

ZEND_API int zend_gc_collect_cycles(void)
{
/* Keep the modified stack outside the function containing setjmp(), so it remains
* valid when freeing traversal storage after a bailout. */
gc_stack stack;

stack.prev = NULL;
stack.next = NULL;
return zend_gc_collect_cycles_with_stack(&stack);
}

ZEND_API void zend_gc_get_status(zend_gc_status *status)
{
status->active = GC_G(gc_active);
Expand Down
Loading