Skip to content

Commit

Permalink
[3.0] 重构 RequestContext (#654)
Browse files Browse the repository at this point in the history
* 重命名请求上下文 getCurrentFlag() => getCurrentId()

* 重构 RequestContext,尽量不污染 Swoole 原有上下文,并且支持 defer()

* 修复、更新文档、测试
  • Loading branch information
Yurunsoft authored Nov 25, 2023
1 parent 6bdd7cc commit f1d3c2e
Show file tree
Hide file tree
Showing 10 changed files with 247 additions and 107 deletions.
18 changes: 18 additions & 0 deletions doc/core/requestContext.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,21 @@ $result = \Imi\RequestContext::remember('myKey3', function () {
return 1 + 2;
});
```

### 推迟执行

当协程释放时触发,先进后出

```php
use function Yurun\Swoole\Coroutine\goWait;
$result = [];
goWait(static function () use (&$result): void {
RequestContext::defer(static function () use (&$result): void {
$result[] = 1;
});
RequestContext::defer(static function () use (&$result): void {
$result[] = 2;
});
}, -1, true);
var_dump($result); // [2, 1]
```
138 changes: 76 additions & 62 deletions src/Components/swoole/src/Context/CoroutineContextManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

namespace Imi\Swoole\Context;

use Imi\Core\Context\ContextData;
use Imi\Core\Context\Contract\IContextManager;
use Imi\Core\Context\Exception\ContextExistsException;
use Imi\Core\Context\Exception\ContextNotFoundException;
use Imi\Event\Event;
use Imi\Log\Log;
use Imi\Swoole\Util\Coroutine;

/**
Expand All @@ -19,58 +19,88 @@ class CoroutineContextManager implements IContextManager
/**
* 上下文对象集合.
*
* @var \ArrayObject[]
* @var ContextData[]
*/
private array $contexts = [];

/**
* {@inheritDoc}
*/
public function create(string $flag, array $data = []): \ArrayObject
public function create(string|int $id, array $data = []): ContextData
{
if ($flag > -1)
if ($id > -1)
{
$context = Coroutine::getContext((int) $flag);
$swooleContext = Coroutine::getContext((int) $id);
// destroy
if (!($context['__bindDestroy'] ?? false))
if (!($swooleContext[static::class]['destroyBinded'] ?? false))
{
$context['__bindDestroy'] = true;
Coroutine::defer($this->__destroy(...));
$swooleContext[static::class]['destroyBinded'] = true;
Coroutine::defer(fn () => $this->destroy($id));
}
if ($data)
$context = $swooleContext[static::class]['context'] ?? null;
if ($context)
{
foreach ($data as $k => $v)
if ($data)
{
$context[$k] = $v;
foreach ($data as $k => $v)
{
$context[$k] = $v;
}
}
}
else
{
$context = $swooleContext[static::class]['context'] = new ContextData($data);
}

return $context;
}
else
{
if (isset($this->contexts[$flag]))
if (isset($this->contexts[$id]))
{
throw new ContextExistsException(sprintf('Context %s already exists!', $flag));
throw new ContextExistsException(sprintf('Context %s already exists!', $id));
}

return $this->contexts[$flag] = new \ArrayObject($data, \ArrayObject::ARRAY_AS_PROPS);
return $this->contexts[$id] = new ContextData($data);
}
}

/**
* {@inheritDoc}
*/
public function destroy(string $flag): bool
public function destroy(string|int $id): bool
{
if ($flag > -1)
if ($id > -1)
{
return false; // 协程退出时自动销毁,无法手动销毁
$swooleContext = Coroutine::getContext((int) $id);
if (!isset($swooleContext[static::class]['context']))
{
return false;
}
// TODO: 实现新的连接管理器后移除
Event::trigger('IMI.REQUEST_CONTENT.DESTROY');
/** @var ContextData $context */
$context = $swooleContext[static::class]['context'];
$deferCallbacks = $context->getDeferCallbacks();
while (!$deferCallbacks->isEmpty())
{
$deferCallbacks->pop()();
}
unset($swooleContext[static::class]);

return true;
}
elseif (isset($this->contexts[$flag]))
elseif (isset($this->contexts[$id]))
{
// TODO: 实现新的连接管理器后移除
Event::trigger('IMI.REQUEST_CONTENT.DESTROY');
unset($this->contexts[$flag]);
$deferCallbacks = $this->contexts[$id]->getDeferCallbacks();
while (!$deferCallbacks->isEmpty())
{
$deferCallbacks->pop()();
}
unset($this->contexts[$id]);

return true;
}
Expand All @@ -83,82 +113,66 @@ public function destroy(string $flag): bool
/**
* {@inheritDoc}
*/
public function get(string $flag, bool $autoCreate = false): \ArrayObject
public function get(string|int $id, bool $autoCreate = false): ContextData
{
if ($flag > -1)
if ($id > -1)
{
$context = Coroutine::getContext((int) $flag);
$swooleContext = Coroutine::getContext((int) $id);
// destroy
if (!($context['__bindDestroy'] ?? false))
if (!($swooleContext[static::class]['destroyBinded'] ?? false))
{
$context['__bindDestroy'] = true;
Coroutine::defer($this->__destroy(...));
$swooleContext[static::class]['destroyBinded'] = true;
Coroutine::defer(fn () => $this->destroy($id));
}

return $context;
if (!isset($swooleContext[static::class]['context']))
{
if ($autoCreate)
{
return $swooleContext[static::class]['context'] = new ContextData();
}
throw new ContextNotFoundException(sprintf('Context %s does not exists!', $id));
}

return $swooleContext[static::class]['context'];
}
else
{
if (!isset($this->contexts[$flag]))
if (!isset($this->contexts[$id]))
{
if ($autoCreate)
{
return $this->create($flag);
return $this->create($id);
}
throw new ContextNotFoundException(sprintf('Context %s does not exists!', $flag));
throw new ContextNotFoundException(sprintf('Context %s does not exists!', $id));
}

return $this->contexts[$flag];
return $this->contexts[$id];
}
}

/**
* {@inheritDoc}
*/
public function exists(string $flag): bool
public function exists(string|int $id): bool
{
if ($flag > -1)
if ($id > -1)
{
return Coroutine::exists($flag);
$swooleContext = Coroutine::getContext((int) $id);

return $swooleContext && isset($swooleContext[static::class]['context']);
}
else
{
return isset($this->contexts[$flag]);
return isset($this->contexts[$id]);
}
}

/**
* {@inheritDoc}
*/
public function getCurrentFlag(): string
public function getCurrentId(): string|int
{
return (string) Coroutine::getCid();
}

/**
* 销毁当前请求的上下文.
*
* 不要手动调用!不要手动调用!不要手动调用!
*/
public function __destroy(): void
{
try
{
Event::trigger('IMI.REQUEST_CONTENT.DESTROY');
$context = Coroutine::getContext();
if (!$context)
{
$coId = Coroutine::getCid();
$contextMap = &$this->contextMap;
if (isset($contextMap[$coId]))
{
unset($contextMap[$coId]);
}
}
}
catch (\Throwable $th)
{
Log::error($th);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Imi\Swoole\Test\Component\Tests;

use Imi\RequestContext;
use Imi\Test\BaseTest;

use function Yurun\Swoole\Coroutine\goWait;

/**
* @testdox RequestContext
*/
class RequestContextTest extends BaseTest
{
public function testDefer(): void
{
$result = [];
goWait(static function () use (&$result): void {
RequestContext::defer(static function () use (&$result): void {
$result[] = 1;
});
RequestContext::defer(static function () use (&$result): void {
$result[] = 2;
});
}, -1, true);
$this->assertEquals([2, 1], $result);
}

public function testRemember(): void
{
$key = 'test_remember';
$count = 0;
$countFun = static function () use (&$count) {
return ++$count;
};

RequestContext::unset($key);
$this->assertEquals(0, $count);
$this->assertEquals(1, RequestContext::remember($key, $countFun));
$this->assertEquals(1, $count);
$this->assertEquals(1, RequestContext::remember($key, $countFun));
$this->assertEquals(1, $count);
RequestContext::unset($key);
$this->assertEquals(2, RequestContext::remember($key, $countFun));
$this->assertEquals(2, $count);
}
}
32 changes: 32 additions & 0 deletions src/Core/Context/ContextData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Imi\Core\Context;

class ContextData extends \ArrayObject
{
protected \SplStack $deferCallbacks;

public function __construct(mixed $input = [])
{
parent::__construct($input, self::ARRAY_AS_PROPS, \ArrayIterator::class);
$this->deferCallbacks = new \SplStack();
}

/**
* 推迟执行,当协程释放时触发,先进后出.
*/
public function defer(callable $callback): void
{
$this->deferCallbacks[] = $callback;
}

/**
* 获取推迟执行任务栈.
*/
public function getDeferCallbacks(): \SplStack
{
return $this->deferCallbacks;
}
}
12 changes: 7 additions & 5 deletions src/Core/Context/Contract/IContextManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,32 @@

namespace Imi\Core\Context\Contract;

use Imi\Core\Context\ContextData;

interface IContextManager
{
/**
* 创建上下文.
*/
public function create(string $flag, array $data = []): \ArrayObject;
public function create(string|int $id, array $data = []): ContextData;

/**
* 销毁上下文.
*/
public function destroy(string $flag): bool;
public function destroy(string|int $id): bool;

/**
* 获取上下文.
*/
public function get(string $flag, bool $autoCreate = false): \ArrayObject;
public function get(string|int $id, bool $autoCreate = false): ContextData;

/**
* 上下文是否存在.
*/
public function exists(string $flag): bool;
public function exists(string|int $id): bool;

/**
* 获取当前上下文标识.
*/
public function getCurrentFlag(): string;
public function getCurrentId(): string|int;
}
Loading

0 comments on commit f1d3c2e

Please sign in to comment.