# TrueAsync w Laravel: architektura prawdziwej konkurencyjności bez Swoole
Tradycyjne rozwiązania dla asynchroniczności w PHP, takie jak Swoole czy RoadRunner, wymagają głębokiej modyfikacji kodu i wprowadzenia kolorowania funkcji. TrueAsync proponuje zupełnie inny podejście — modyfikację jądra Zend dla przezroczystej asynchroniczności. Rozłożymy na części pierwsze, jak dostosować Laravel do tego modelu, pokonując problemy usług stateful i zapewniając konkurencyjne przetwarzanie operacji związanych z wejściem/wyjściem.
Podstawy TrueAsync: jak działa przezroczysta asynchroniczność
TrueAsync to alternatywne jądro PHP, które modyfikuje Zend Engine, biblioteki I/O i pracę z bazami danych. Jego kluczową zaletą jest brak problemu kolorowania funkcji. W przeciwieństwie do Node.js czy Python asyncio, zwykłe funkcje PHP (PDO::query(), file_get_contents()) automatycznie oddają sterowanie planistowi w momencie oczekiwania na I/O. Korutiny są wstrzymywane na poziomie funkcji C, co pozwala na obsługę setek żądań w jednym wątku.
Ważne jest zrozumienie: to nie wielowątkowość. Model opiera się na kooperatywnym wielozadaniowości — przełączaniu kontekstów podczas oczekiwania na zewnętrzne zasoby. Dla zadań związanych z CPU (ciężkie obliczenia) rozwiązanie nie jest efektywne, ale idealnie nadaje się do typowych scenariuszy webowych z zapytaniami do baz danych i API. Przykład podstawowego użycia:
use function Async\spawn;
use function Async\await;
use function Async\delay;
$a = spawn(function() {
delay(100);
return 'result A';
});
$b = spawn(function() {
delay(100);
return 'result B';
});
// Obschee time vypolneniya ~100ms
echo await($a);
echo await($b);
Problem usług stateful w Laravel
Laravel został pierwotnie zaprojektowany pod model FPM, gdzie każde żądanie jest obsługiwane w izolowanym procesie. Usługi stateful (AuthManager, Session, Router) przechowują stan w właściwościach obiektów, które są niszczone po obsłużeniu żądania. W trybie async proces żyje stale, co prowadzi do wycieków stanu:
// Coroutine A: request from user_1
Auth::loginUsingId(1);
delay(200);
// Coroutine B: request from user_2
Auth::loginUsingId(2);
// Return in korutinu A
echo Auth::id(); // Vozvraschaet 2 vmesto 1
Statyczne właściwości (Model::$dispatcher, Facade::$resolvedInstance) i usługi w kontenerze IoC stają się wspólne dla wszystkich żądań. Octane rozwiązuje problem przez klonowanie kontenera, ale to generuje narzut i nie obejmuje statycznych właściwości.
Izolacja kontekstowa: architektura rozwiązania
TrueAsync udostępnia current_context() — magazyn danych powiązany z cyklem życia żądania. Po zakończeniu korutiny kontekst jest automatycznie czyszczony. Architektura dzieli Laravel na dwie strefy:
- Statyczne jądro: router, konfiguracja, kontener IoC. Ładowane raz, wspólne dla wszystkich żądań.
- Usługi per-żądanie: Auth, Session, Cookie. Tworzone w kontekście każdej korutiny.
Kluczowy mechanizm to przechwycenie rozwiązywania usług przez AsyncApplication::resolve():
protected function resolve($abstract, ...)
{
if ($this->asyncMode && $this->isScoped($abstract)) {
$ctx = current_context();
if ($instance = $ctx->find($abstract)) {
return $instance;
}
$instance = $this->buildFresh($abstract);
$ctx->set($abstract, $instance);
return $instance;
}
return parent::resolve($abstract, ...);
}
Klucze kontekstowe przez enum i rozwiązanie dla Facades
Aby uniknąć kolizji i zapewnić bezpieczeństwo dostępu, użyto kluczy enum:
enum ScopedService: string
{
case REQUEST = 'request';
case SESSION = 'session';
case AUTH = 'auth';
case AUTH_DRIVER = 'auth.driver';
case COOKIE = 'cookie';
}
To daje:
- Izolację dostępu (tylko właściciel enum może czytać/pisać dane)
- Gwarancję braku kolizji (nawet przy identycznych wartościach stringowych)
- Obsługę statycznej analizy (PHPStan/IDE śledzą użycie)
Fasady wymagały osobnego rozwiązania z powodu buforowania resolvedInstance w statycznej tablicy. Wdrożono ScopedServiceProxy:
class ScopedServiceProxy
{
public function __call($method, $args)
{
return ($this->resolver)()->$method(...$args);
}
}
Proxy przekierowuje wywołania do kontekstu bieżącej korutiny, zachowując bufor na poziomie proxy.
Krytyczne adaptacje i PDO Pool
Po bazowej izolacji zidentyfikowano usługi przechowujące stan per-żądanie wewnątrz siebie. Wymagały specjalistycznych wersji async:
- AsyncRouter (izolacja bieżącej trasy)
- AsyncDispatcher (odroczone zdarzenia per-korutina)
- AsyncTranslator (lokalizacja tłumaczeń)
- AsyncViewFactory (View::share())
- AsyncConfig (zmiany konfiguracji w runtime)
- AsyncDatabaseSessionHandler (atomowy zapis sesji)
- Async\*Connection (licznik transakcji)
Szczególna uwaga na pracę z bazami danych. Bez puli połączeń jednoczesne zapytania od korutin psują protokół PostgreSQL. TrueAsync rozwiązuje to wbudowanym PDO Pool:
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_POOL_ENABLED => true,
PDO::ATTR_POOL_MIN => 0,
PDO::ATTR_POOL_MAX => 10,
PDO::ATTR_POOL_HEALTHCHECK_INTERVAL => 30,
]);
Pula dynamicznie dystrybuuje połączenia: po otrzymaniu wyniku SELECT połączenie wraca do puli, obsługując więcej korutin niż maksymalny rozmiar puli. Dla transakcji połączenie jest blokowane do commit/rollback.
Co ważne
- TrueAsync eliminuje problem kolorowania funkcji przez modyfikację jądra PHP
- Usługi stateful Laravel wymagają izolacji kontekstowej przez current_context()
- PDO Pool jest kluczowy dla poprawnej pracy z bazami danych w środowisku konkurencyjnym
- Klucze enum zapewniają bezpieczeństwo i śledzalność danych kontekstowych
- Rozwiązanie nie zastępuje wielowątkowości dla zadań związanych z CPU
— Editorial Team
Brak komentarzy.