diff --git a/bench-func/bench_full.sh b/bench-func/bench_full.sh index 0df3967..3311c4a 100644 --- a/bench-func/bench_full.sh +++ b/bench-func/bench_full.sh @@ -788,8 +788,8 @@ function sieve($n){$s=array_fill(0,$n+1,true);$s[0]=$s[1]=false;for($i=2;$i*$i<= function sqrtSum($n){$s=0;for($i=1;$i<=$n;$i++)$s+=sqrt($i);return $s;} function bubbleSort($arr){$n=count($arr);for($i=0;$i<$n;$i++)for($j=0;$j<$n-$i-1;$j++)if($arr[$j]>$arr[$j+1])[$arr[$j],$arr[$j+1]]=[$arr[$j+1],$arr[$j]];return $arr;} function matMul($a,$b){$ra=count($a);$ca=count($a[0]);$cb=count($b[0]);$c=array_fill(0,$ra,array_fill(0,$cb,0));for($i=0;$i<$ra;$i++)for($j=0;$j<$cb;$j++)for($k=0;$k<$ca;$k++)$c[$i][$j]+=$a[$i][$k]*$b[$k][$j];return $c;} -function processWords($w){return array_values(array_map(fn($s)=>strrev(strtoupper($s)),array_filter($w,fn($s)=>strlen($s)>3)));} -function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(fn($x)=>($x-$m)**2,$arr))/$n;return['n'=>$n,'mean'=>round($m,4),'std'=>round(sqrt($v),4)];} +function processWords($w){$f=array_filter($w,function($s){return strlen($s)>3;});return array_values(array_map(function($s){return strrev(strtoupper($s));},$f));} +function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(function($x)use($m){return($x-$m)**2;},$arr))/$n;return['n'=>$n,'mean'=>round($m,4),'std'=>round(sqrt($v),4)];} function main($event,$context){ $t0=microtime(true); @@ -818,10 +818,10 @@ function trigSum($n){$s=0;for($i=0;$i<$n;$i++)$s+=sin($i)*cos($i);return $s;} function bubbleSort($arr){$n=count($arr);for($i=0;$i<$n;$i++)for($j=0;$j<$n-$i-1;$j++)if($arr[$j]>$arr[$j+1])[$arr[$j],$arr[$j+1]]=[$arr[$j+1],$arr[$j]];return $arr;} function insertionSort($arr){for($i=1;$i=0&&$arr[$j]>$k){$arr[$j+1]=$arr[$j];$j--;}$arr[$j+1]=$k;}return $arr;} function matMul($a,$b){$ra=count($a);$ca=count($a[0]);$cb=count($b[0]);$c=array_fill(0,$ra,array_fill(0,$cb,0));for($i=0;$i<$ra;$i++)for($j=0;$j<$cb;$j++)for($k=0;$k<$ca;$k++)$c[$i][$j]+=$a[$i][$k]*$b[$k][$j];return $c;} -function processWords($w){return array_values(array_map(fn($s)=>['original'=>$s,'upper'=>strtoupper($s),'reversed'=>strrev($s),'len'=>strlen($s),'vowels'=>preg_match_all('/[aeiou]/',$s)],array_filter($w,fn($s)=>strlen($s)>=3)));} -function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(fn($x)=>($x-$m)**2,$arr))/$n;return['n'=>$n,'min'=>min($arr),'max'=>max($arr),'mean'=>round($m,4),'std'=>round(sqrt($v),4)];} -function caesar($t,$sh){return preg_replace_callback('/[a-zA-Z]/',fn($m)=>chr((ord($m[0])-(ctype_lower($m[0])?97:65)+$sh)%26+(ctype_lower($m[0])?97:65)),$t);} -function lookupOps($n){$sq=array_combine(range(0,199),array_map(fn($i)=>$i*$i,range(0,199)));$cu=array_combine(range(0,199),array_map(fn($i)=>$i**3,range(0,199)));$r=0;for($i=0;$i<$n;$i++)$r+=($sq[$i%200]??0)+($cu[$i%200]??0);return $r;} +function processWords($w){$f=array_filter($w,function($s){return strlen($s)>=3;});return array_values(array_map(function($s){return['original'=>$s,'upper'=>strtoupper($s),'reversed'=>strrev($s),'len'=>strlen($s),'vowels'=>preg_match_all('/[aeiou]/',$s)];},$f));} +function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(function($x)use($m){return($x-$m)**2;},$arr))/$n;return['n'=>$n,'min'=>min($arr),'max'=>max($arr),'mean'=>round($m,4),'std'=>round(sqrt($v),4)];} +function caesar($t,$sh){return preg_replace_callback('/[a-zA-Z]/',function($m)use($sh){return chr((ord($m[0])-(ctype_lower($m[0])?97:65)+$sh)%26+(ctype_lower($m[0])?97:65));},$t);} +function lookupOps($n){$sq=array_combine(range(0,199),array_map(function($i){return $i*$i;},range(0,199)));$cu=array_combine(range(0,199),array_map(function($i){return $i**3;},range(0,199)));$r=0;for($i=0;$i<$n;$i++)$r+=($sq[$i%200]??0)+($cu[$i%200]??0);return $r;} function main($event,$context){ $words=$GLOBALS['WORDS']; @@ -833,8 +833,8 @@ function main($event,$context){ $h256=substr(hash('sha256','bench-large-php-2026'),0,16); $md5=substr(hash('md5','bench-large-php-2026'),0,16); $b64=base64_encode('hello from bench-php-large'); - $ma=array_map(fn($i)=>array_map(fn($j)=>$i+$j,range(0,3)),range(0,3)); - $mb=array_map(fn($i)=>array_map(fn($j)=>$i*$j+1,range(0,3)),range(0,3)); + $ma=array_map(function($i){return array_map(function($j)use($i){return $i+$j;},range(0,3));},range(0,3)); + $mb=array_map(function($i){return array_map(function($j)use($i){return $i*$j+1;},range(0,3));},range(0,3)); $mc=matMul($ma,$mb); $pw=processWords($words);$pst=statsArr(array_slice($primes,0,50)); $cipher=caesar('hello bench world',13); @@ -845,7 +845,7 @@ function main($event,$context){ 'sqrt'=>round($sq,2),'log'=>round($lg,2),'trig'=>round($tr,4), 'primes'=>count($primes),'fib40'=>$f40,'fibs_sum'=>array_sum($fibs), 'h256'=>$h256,'md5'=>$md5,'b64_len'=>strlen($b64), - 'mat_trace'=>array_sum(array_map(fn($i)=>$mc[$i][$i],range(0,3))), + 'mat_trace'=>array_sum(array_map(function($i)use($mc){return $mc[$i][$i];},range(0,3))), 'words'=>count($pw),'stats'=>$pst,'cipher'=>$cipher, 'lookup'=>$lookup,'csum'=>round($csum,6),'elapsed'=>round($elapsed,5)]); } diff --git a/bench-func/diag_bugs.sh b/bench-func/diag_bugs.sh new file mode 100644 index 0000000..8ab79bd --- /dev/null +++ b/bench-func/diag_bugs.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +# diag_bugs.sh — диагностика двух аномалий из bench_full run=9248232 +# BUG-1: php/medium + php/large COLD-TIMEOUT (гипотеза: arrow functions PHP 7.4+) +# BUG-2: ruby/medium cold=80451ms (гипотеза: poolmgr eviction — проверяем воспроизводимость) + +set -uo pipefail + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +source "${ROOT_DIR}/scripts/lib.sh" + +SUB="bench@bench.local" +RUNID="diag$(date +%s | tail -c 6)" +TMP=$(mktemp -d) + +TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S) +RESULTS_DIR="${ROOT_DIR}/test-results/bench-func" +LOGFILE="${RESULTS_DIR}/diag_bugs_${TIMESTAMP}.log" +mkdir -p "$RESULTS_DIR" + +declare -a ALL_FNS=() +PASS_CNT=0 +FAIL_CNT=0 + +cleanup() { + rm -rf "$TMP" 2>/dev/null || true + for fn in "${ALL_FNS[@]:-}"; do + curl -s -X DELETE "${BASE}/functions/${fn}" \ + -H "X-Auth-Token: ${SUB}" >/dev/null 2>&1 || true + done +} +trap cleanup EXIT + +log() { echo "$@" | tee -a "$LOGFILE"; } +escape_code() { python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))'; } +get_status() { + python3 -c "import json,sys +try: d=json.load(sys.stdin); print(d.get('status','')) +except: print('')" 2>/dev/null +} +get_field() { + local f="$1" + python3 -c "import json,sys +try: d=json.load(sys.stdin); print(d.get('$f','')) +except: print('')" 2>/dev/null +} + +log "════════════════════════════════════════════════════════════════" +log " diag_bugs.sh run=${RUNID} $(date)" +log " API: ${BASE} SUB: ${SUB}" +log "════════════════════════════════════════════════════════════════" + +# ═══════════════════════════════════════════════════════════════════ +# Вспомогательная: загрузить функцию, сделать cold+hot invoke, вернуть результат +# bench_one LABEL LANG CODEFILE [EXPECTED_STR] +# ═══════════════════════════════════════════════════════════════════ +bench_one() { + local label="$1" lang="$2" codefile="$3" expected="${4:-}" + local fn="bench-${label}-${RUNID}" + ALL_FNS+=("$fn") + + # Upload + local code_esc t0 t1 R CODE BODY UPLOAD_MS + code_esc=$(cat "$codefile" | escape_code) + t0=$(date +%s%3N) + R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \ + -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \ + -d "{\"name\":\"${fn}\",\"language\":\"${lang}\",\"code\":${code_esc}}" 2>/dev/null) + t1=$(date +%s%3N) + CODE=$(echo "$R" | tail -1) + BODY=$(echo "$R" | head -n -1) + UPLOAD_MS=$((t1-t0)) + + if [ "$CODE" != "201" ]; then + log " [${label}] UPLOAD FAIL HTTP=${CODE}: $(echo "$BODY" | head -c 200)" + FAIL_CNT=$((FAIL_CNT+1)) + return 1 + fi + log " [${label}] upload OK ${UPLOAD_MS}ms" + + # Cold start (retry 20×5s) + local tc0 COLD_MS=0 cold_ok=0 cold_body="" + tc0=$(date +%s%3N) + for i in $(seq 1 20); do + local resp s raw + resp=$(curl -s --max-time 35 -X POST "${BASE}/functions/${fn}/invoke" \ + -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}' 2>/dev/null) + s=$(echo "$resp" | get_status) + raw=$(echo "$resp" | get_field "response_raw") + if [ "$s" = "200" ]; then + if [ -z "$expected" ] || echo "$raw" | grep -qF "$expected"; then + local tc1; tc1=$(date +%s%3N) + COLD_MS=$((tc1-tc0)) + cold_ok=1 + cold_body="$raw" + break + fi + fi + sleep 5 + done + + if [ $cold_ok -eq 0 ]; then + log " [${label}] COLD TIMEOUT" + FAIL_CNT=$((FAIL_CNT+1)) + return 1 + fi + + # Hot ×3 + local HOT_TOTAL=0 hot_ok=0 + for i in 1 2 3; do + local th0 th1 resp s + th0=$(date +%s%3N) + resp=$(curl -s --max-time 35 -X POST "${BASE}/functions/${fn}/invoke" \ + -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}' 2>/dev/null) + th1=$(date +%s%3N) + s=$(echo "$resp" | get_status) + [ "$s" = "200" ] && { HOT_TOTAL=$((HOT_TOTAL+th1-th0)); hot_ok=$((hot_ok+1)); } + done + local HOT_AVG=0; [ $hot_ok -gt 0 ] && HOT_AVG=$((HOT_TOTAL/hot_ok)) + + log " [${label}] OK cold=${COLD_MS}ms hot_avg=${HOT_AVG}ms (${hot_ok}/3)" + [ -n "$cold_body" ] && log " [${label}] body=$(echo "$cold_body" | head -c 120)" + PASS_CNT=$((PASS_CNT+1)) +} + +# ═══════════════════════════════════════════════════════════════════ +# BUG-1: PHP arrow functions +# ═══════════════════════════════════════════════════════════════════ +log "" +log "┌─────────────────────────────────────────────────────────────────┐" +log "│ BUG-1: PHP arrow functions (fn()=> требует PHP 7.4+) │" +log "└─────────────────────────────────────────────────────────────────┘" + +# Тест 1а: минимальный PHP с arrow function — поддерживается? +cat > "${TMP}/php_arrow_min.php" << 'EOF' + $x * 2, $nums); + return "arrow-ok:" . implode(",", $doubled); +} +EOF +log "" +log " [php-arrow-min] Тест: arrow function fn(\$x)=>\$x*2 (PHP 7.4+)" +bench_one "php-arrow-min" "php" "${TMP}/php_arrow_min.php" "arrow-ok" + +# Тест 1б: PHP version info через phpinfo-like +cat > "${TMP}/php_version.php" << 'EOF' + "${TMP}/php_medium_noarrow.php" << 'EOF' +$arr[$j+1]){$t=$arr[$j];$arr[$j]=$arr[$j+1];$arr[$j+1]=$t;}return $arr;} +function matMul($a,$b){$ra=count($a);$ca=count($a[0]);$cb=count($b[0]);$c=array_fill(0,$ra,array_fill(0,$cb,0));for($i=0;$i<$ra;$i++)for($j=0;$j<$cb;$j++)for($k=0;$k<$ca;$k++)$c[$i][$j]+=$a[$i][$k]*$b[$k][$j];return $c;} +function processWordsNoArrow($words){ + $out=[]; + foreach($words as $s){ + if(strlen($s)>3) $out[]=strrev(strtoupper($s)); + } + return $out; +} +function statsArrNoArrow($arr){ + $n=count($arr);$m=array_sum($arr)/$n; + $v=0; foreach($arr as $x) $v+=($x-$m)*($x-$m); $v/=$n; + return['n'=>$n,'mean'=>round($m,4),'std'=>round(sqrt($v),4)]; +} + +function main($event,$context){ + $t0=microtime(true); + $words=["alpha","beta","gamma","delta","epsilon","zeta","eta","theta","iota","kappa","lambda","mu"]; + $f=fib(35);$primes=sieve(500);$s=sqrtSum(1000); + $nums=range(50,1);$sorted=bubbleSort($nums); + $a=[[1,2,3],[4,5,6]];$b=[[7,8],[9,10],[11,12]];$c=matMul($a,$b); + $pw=processWordsNoArrow($words);$st=statsArrNoArrow($primes); + $h=substr(hash('sha256','bench-medium-php'),0,16); + $elapsed=microtime(true)-$t0; + return json_encode(['tag'=>'bench-php-medium-noarrow-ok','fib35'=>$f,'primes'=>count($primes), + 'sqrt'=>round($s,1),'sorted0'=>$sorted[0],'mat_c00'=>$c[0][0], + 'words'=>count($pw),'stats'=>$st,'hash'=>$h,'elapsed'=>round($elapsed,5)]); +} +EOF +log "" +log " [php-medium-noarrow] PHP medium логика без arrow functions (foreach вместо fn()=>)" +bench_one "php-med-noarr" "php" "${TMP}/php_medium_noarrow.php" "bench-php-medium-noarrow-ok" + +# ═══════════════════════════════════════════════════════════════════ +# BUG-2: ruby/medium cold=80451ms — воспроизводимость +# ═══════════════════════════════════════════════════════════════════ +log "" +log "┌─────────────────────────────────────────────────────────────────┐" +log "│ BUG-2: ruby/medium cold=80451ms — воспроизводимость (×3) │" +log "└─────────────────────────────────────────────────────────────────┘" + +cat > "${TMP}/ruby_medium_diag.rb" << 'EOF' +require 'json' +require 'digest' + +WORDS=%w[alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega] + +def fib(n); a,b=0,1; n.times{a,b=b,a+b}; a; end +def sieve(n) + s=Array.new(n+1,true); s[0]=s[1]=false + (2..Math.sqrt(n).to_i).each{|i|(i*i..n).step(i){|j|s[j]=false} if s[i]} + (0..n).select{|i|s[i]} +end +def sqrt_sum(n); (1..n).sum{|i|Math.sqrt(i)}; end +def bubble_sort(arr) + arr=arr.dup; n=arr.length + n.times{(n-1).times{|j|arr[j],arr[j+1]=arr[j+1],arr[j] if arr[j]>arr[j+1]}} + arr +end +def mat_mul(a,b) + ra,ca,cb=a.length,a[0].length,b[0].length + Array.new(ra){|i|Array.new(cb){|j|(0...ca).sum{|k|a[i][k]*b[k][j]}}} +end +def process_words(w); w.select{|s|s.length>3}.map{|s|s.upcase.reverse}; end +def get_stats(arr) + n=arr.length; m=arr.sum.to_f/n; v=arr.sum{|x|(x-m)**2}/n + {n:n, mean:m.round(4), std:Math.sqrt(v).round(4)} +end + +def handler(ctx) + t0=Time.now + f=fib(35); primes=sieve(500); s=sqrt_sum(1000) + nums=(1..50).to_a.reverse; sorted=bubble_sort(nums) + a=[[1,2,3],[4,5,6]]; b=[[7,8],[9,10],[11,12]]; c=mat_mul(a,b) + pw=process_words(WORDS); st=get_stats(primes) + h=Digest::SHA256.hexdigest('bench-medium-ruby')[0,16] + elapsed=Time.now-t0 + { statusCode: 200, body: JSON.generate({tag:"bench-ruby-medium-ok", fib35:f, primes:primes.length, + sqrt:s.round(1), sorted0:sorted[0], mat_c00:c[0][0], + words:pw.length, stats:st, hash:h, elapsed:elapsed.round(5)}) } +end +EOF + +log "" +log " ruby/medium × 3 независимых функции (разные имена → чистый cold start каждый раз)" +for attempt in 1 2 3; do + log "" + log " --- Попытка ${attempt}/3 ---" + bench_one "ruby-med-${attempt}" "ruby" "${TMP}/ruby_medium_diag.rb" "bench-ruby-medium-ok" +done + +# ═══════════════════════════════════════════════════════════════════ +# ИТОГ +# ═══════════════════════════════════════════════════════════════════ +log "" +log "════════════════════════════════════════════════════════════════" +log " ИТОГ: PASS=${PASS_CNT} FAIL=${FAIL_CNT} TOTAL=$((PASS_CNT+FAIL_CNT))" +log " Лог: ${LOGFILE}" +log " Готово: $(date)" +log "════════════════════════════════════════════════════════════════" diff --git a/doc/report-2026-05-20-bench-php-analysis.md b/doc/report-2026-05-20-bench-php-analysis.md new file mode 100644 index 0000000..6f1d287 --- /dev/null +++ b/doc/report-2026-05-20-bench-php-analysis.md @@ -0,0 +1,161 @@ +# Анализ бенчмарка bench_full.sh — 2026-05-20 + +## Контекст + +Запущен полный бенчмарк `bench-func/bench_full.sh` (RUNID=9248232). +Namespace: `fission-08f114ea3595cccf` (sub: bench@bench.local). +4 языка × 4 размера (tiny/small/medium/large) + 4 архивных теста = 20 итого. + +--- + +## Результаты bench_full.sh + +| LANG | SIZE | UPLOAD(ms) | COLD(ms) | HOT_AVG(ms) | STATUS | +|--------|---------|-----------|---------|------------|----------------| +| python | tiny | 2529 | 3317 | 1198 | OK | +| python | small | 2764 | 1387 | 1182 | OK | +| python | medium | 2762 | 1374 | 1185 | OK | +| python | large | 2769 | 1366 | 1653 | OK | +| nodejs | tiny | 3158 | 9410 | 1172 | OK (аномалия*) | +| nodejs | small | 2751 | 1559 | 1193 | OK | +| nodejs | medium | 2754 | 2471 | 1158 | OK | +| nodejs | large | 2745 | 1583 | 1190 | OK | +| ruby | tiny | 3142 | 3255 | 1162 | OK | +| ruby | small | 2737 | 1402 | 1181 | OK | +| ruby | medium | 3151 | **80451** | 115 | OK (аномалия!) | +| ruby | large | 1116 | 2448 | 104 | OK | +| php | tiny | 1137 | 2528 | 1200 | OK | +| php | small | 2747 | 1469 | 1158 | OK | +| php | medium | 2749 | — | — | **COLD-TIMEOUT** | +| php | large | 2725 | — | — | **COLD-TIMEOUT** | +| python | archive | 2749 | 1403 | 1176 | OK | +| nodejs | archive | 2777 | 1539 | 535 | OK | +| ruby | archive | 1367 | 821 | 787 | OK | +| php | archive | 2744 | 1575 | 1189 | OK | + +**ИТОГ: PASS=18, FAIL=2** + +*nodejs/tiny cold=9410ms — одиночный выброс, не воспроизводился. + +--- + +## Аномалия: ruby/medium cold=80451ms + +### Анализ +Проведено ×6 независимых прогонов (diag_bugs.sh, два запуска): +- 3780 / 1402 / 1384 / 3225 / 1435 / 1773 ms + +**Вывод:** не воспроизводится. Разовый выброс (вероятно node pressure или transient network). +**Статус: ЗАКРЫТ. Не баг.** + +--- + +## Баг: PHP medium + large COLD-TIMEOUT + +### Первичная гипотеза (верна) +`bench_full.sh` генерирует PHP код для medium/large с arrow functions (PHP 7.4+): +```php +// medium +array_map(fn($s) => strrev(strtoupper($s)), $words) +array_map(fn($x) => ($x - $m) ** 2, $data) + +// large +array_map(fn($i) => array_map(fn($j) => ..., ...), ...) +``` +PHP env на кластере — версия **< 7.4**. Arrow syntax → Parse error → pod возвращает 500. + +PHP tiny/small и PHP archive — используют традиционный синтаксис без `fn()=>` → OK. + +### Механизм pool exhaustion (важно!) + +При invoke сломанной функции: +1. Executor берёт pool pod → специализирует под функцию +2. PHP парсер: Parse error → возвращает 500 +3. Executor считает это ошибкой вызова → ретраит следующий pod +4. 20 ретраев × 5s = 100s → COLD TIMEOUT +5. За это время пул (`console-php-env`) в namespace исчерпан + +**Следствие:** php/large идёт ПОСЛЕ php/medium. К моменту его запуска пул уже разрушен → php/large тоже COLD TIMEOUT даже если бы его код был правильным. + +### Диагностика diag_bugs.sh — что мы делали + +Создан скрипт `bench-func/diag_bugs.sh` с 6 тестами: +1. `php-arrow-min` — минимальный PHP с `fn($x)=>$x*2` +2. `php-version` — только `return PHP_VERSION` (стандартный PHP) +3. `php-med-noarr` — medium логика через foreach вместо arrow +4. `ruby-med-1/2/3` — ruby/medium ×3 независимых функции + +### Ошибки в процессе диагностики + +**Ошибка 1: SIGPIPE при `| tee`** +Первый прогон завершился досрочно — скрипт через SSH pipe убивался SIGPIPE при прокрутке/чтении терминала. Исправлено: nohup + прямая запись в файл. + +**Ошибка 2: 5 параллельных прогонов diag_bugs.sh** +В процессе диагностики случайно накопилось 5+ одновременных процессов `diag_bugs.sh` на VM (PIDs: 2476997, 2478955, 2480445, 2482045, 2482452). Все конкурировали за PHP pool → дополнительный pool exhaustion. + +**Следствие:** тесты `php-version` и `php-med-noarr` тоже дали COLD TIMEOUT — не из-за собственного бага, а из-за занятого пула. + +### Что реально подтверждено vs что предполагается + +| Факт | Статус | +|------|--------| +| `fn()=>` вызывает COLD TIMEOUT | **ПОДТВЕРЖДЕНО** (×2 прогона) | +| PHP env версия < 7.4 | **ПРЕДПОЛОЖЕНИЕ** (php-version не дал ответа из-за pool exhaustion) | +| php-version FAIL = самостоятельный баг | **ОПРОВЕРГНУТО** — следствие pool exhaustion | +| php-med-noarr FAIL = PHP env broken | **НЕ ПОДТВЕРЖДЕНО** — тест не прошёл чисто | + +### Что НЕ удалось выяснить +- Точная версия PHP в env образе +- Работает ли `php-med-noarr` (medium без arrow) в изолированном тесте + +--- + +## Механизм pool exhaustion — общая проблема + +Одна функция с синтаксической ошибкой может "заморозить" весь PHP пул namespace: +1. Сломанная функция → 20 ретраев × пул pod +2. Пул исчерпан → **все остальные PHP функции того же пользователя** получают COLD TIMEOUT +3. Пользователь видит что "рабочая" функция сломалась — не понимает почему + +В реальном проде это критично: один неосторожный деплой = все PHP функции пользователя не отвечают. + +### Почему pod "держится" +Pool pod ≠ выполнение кода. Pod — контейнер-заготовка. При invoke: +- Executor специализирует pod (загружает код через /specialize) +- PHP парсер падает → 500 → executor ретраит СЛЕДУЮЩИЙ pod +- Каждый ретрай = новый pod из пула + +Pod не "держится" в одном — executor расходует пул по одному pod'у за ретрай. + +### Системные решения (не реализованы) +1. **Circuit breaker в executor** — после N провалов подряд для одной функции → вернуть ошибку, не трогать пул +2. **Синтаксическая валидация при upload** — `php -l`, `python3 -m py_compile`, `ruby -c`, `node --check` — сломанный код не попадает в систему +3. **Pool size limit per namespace** — ограничить max pods на env × namespace + +Вариант 2 (валидация при upload) — реализуем в нашем коде (fission-console), остальные — в fission-bundle. + +--- + +## Предложения — что делать + +### Приоритет 1 (быстро): исправить bench_full.sh +Заменить arrow functions в PHP medium/large на традиционный синтаксис. +- `fn($s) => strrev(strtoupper($s))` → `function($s){ return strrev(strtoupper($s)); }` +- `fn($x) => ($x - $m) ** 2` → foreach цикл +- Аналогично для large (вложенные array_map) + +После фикса запустить bench_full.sh заново — должно быть PASS=20. + +### Приоритет 2 (чистая диагностика): проверить php-med-noarr +Убить фоновые процессы на VM, запустить одиночный тест php-version/php-med-noarr в изоляции — убедиться что PHP env работает для стандартного кода. + +### Приоритет 3 (системный): валидация кода при upload +Добавить в fission-console синтаксическую проверку кода перед созданием функции. Защита от pool exhaustion для всех пользователей. + +--- + +## Файлы + +- `bench-func/bench_full.sh` — основной бенчмарк (требует фикс PHP medium/large) +- `bench-func/diag_bugs.sh` — диагностический скрипт (создан в этой сессии) +- `test-results/bench-func/` — логи прогонов