**Diagnóstico**

La operación concreta que hace que el contador final quede obsoleto es la **separación de referencias (copy-on-write)** del array `progress->data` en la línea 138, seguida por la asignación al puntero `post_bytes_processed` en la línea 140.

En las líneas 69-70 (`php_session_rfc1867_update`), el array `progress->data` se inserta en la sesión global mediante `zend_hash_update`. Esto incrementa su referencia a 2 (una en la estructura de progreso y otra en la sesión). Cuando llega el evento `MULTIPART_EVENT_END` con `cleanup=0`, la línea 138 ejecuta `SEPARATE_ARRAY(&progress->data)`. Esta macro crea una **copia física** del array si las referencias son >1, para evitar mutar la copia que ya está en la sesión.

Sin embargo, el puntero `progress->post_bytes_processed` (definido en la línea 84) apunta a la entrada `"bytes_processed"` dentro de la estructura `zval` original. Al ejecutar `SEPARATE_ARRAY`, se crea un nuevo array, pero **el puntero `progress->post_bytes_processed` no se actualiza** para apuntar a la nueva copia; sigue apuntando al `zval` del array antiguo (o a una posición que ya no es la "fuente de verdad" activa si el GC o el hash reasigna).

En la línea 140, `Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed;` escribe el valor final en el array **antiguo** (o en una copia desincronizada), mientras que la sesión ya contiene el array separado. Aunque `php_session_rfc1867_update(progress, 1)` en la línea 141 fuerza la actualización, si la separación no reasigna correctamente el puntero interno o si hay un desfase en cómo se lee `Z_LVAL_P` tras la copia, el valor escrito puede no reflejarse en la sesión final de forma consistente con el cuerpo total.

En este caso específico (262642 bytes totales, 136576 procesados), el problema es que `data->post_bytes_processed` en el evento END **no incluye los bytes del último campo de formulario grande** si el parser de multipart no ha emitido un evento de datos para ese campo antes del END, o si la separación en línea 138 "congela" el valor anterior. Pero la causa raíz estructural es que `SEPARATE_ARRAY` rompe la sincronización entre el puntero directo y el array exportado a la sesión.

**Parche Mínimo (Unified Diff)**

Para garantizar que el contador final sea igual a la longitud del cuerpo, debemos asegurarnos de que el valor escrito en la línea 140 se refleje en el mismo array que se exporta. La solución más mínima es **forzar la reasignación del puntero** tras la separación o evitar la separación si no es necesaria. Sin embargo, dado que `SEPARATE_ARRAY` es necesario para no mutar la sesión durante el parseo, la corrección es actualizar el puntero después de la copia.

```diff
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@ -137,6 +137,9 @@
 						if (!Z_ISUNDEF(progress->data)) {
 							SEPARATE_ARRAY(&progress->data);
+							progress->post_bytes_processed = zend_hash_str_find(
+								Z_ARRVAL(progress->data), "bytes_processed", 
+								sizeof("bytes_processed") - 1);
 							add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 1);
 							Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed;
 							php_session_rfc1867_update(progress, 1);
```

**Explicación del Parche:**
Tras `SEPARATE_ARRAY`, el array `progress->data` es una nueva instancia. El puntero `progress->post_bytes_processed` aún apunta al `zval` del array anterior. Al re-encontrar la clave `"bytes_processed"` en el nuevo array (`zend_hash_str_find`), aseguramos que la escritura en la línea siguiente afecte al array que realmente se exportará a la sesión en `php_session_rfc1867_update`.

**Lo que establece y no establece la prueba:**

*   **Establece:** Que con `cleanup=0`, el valor final de `bytes_processed` en la sesión debe coincidir con la longitud total del cuerpo multipart (262642 bytes) cuando se completa la carga. Establece que la separación de referencias (`SEPARATE_ARRAY`) puede desincronizar los punteros internos si no se reasignan.
*   **No establece:** No prueba el comportamiento con `cleanup=1` (donde se limpia la sesión al final). No prueba la precisión temporal de las actualizaciones intermedias (solo el valor final). No verifica si el parser de multipart emite correctamente los eventos de datos para campos no-fichero grandes antes del END (aunque en este caso, el parche corrige la escritura final independientemente de eso, asumiendo que `data->post_bytes_processed` en el evento END es correcto).
