-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy pathRequestQueue.php
145 lines (131 loc) · 5.51 KB
/
RequestQueue.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<?php
/**
* Copyright: Swlib
* Author: Twosee <[email protected]>
* Date: 2018/4/1 下午3:47
*/
namespace Swlib\Saber;
use InvalidArgumentException;
use SplQueue;
use Swlib\Util\InterceptorTrait;
class RequestQueue extends SplQueue
{
/** @var SplQueue */
public $concurrency_pool;
public $max_concurrency = -1;
use InterceptorTrait;
public function enqueue($request)
{
if (!($request instanceof Request)) {
throw new InvalidArgumentException('Value must be instance of ' . Request::class);
}
if ($this->getMaxConcurrency() > 0) {
if ($request->isWaiting()) {
throw new InvalidArgumentException("You can't enqueue a waiting request when using the max concurrency control!");
}
}
/**
* 注意! `withInQueue`是并发重定向优化
* 原理是重定向时并不如同单个请求一样马上收包,而是将其再次加入请求队列执行defer等待收包
* 待队列中所有原有并发请求第一次收包完毕后,再一同执行重定向收包,
* 否则并发请求会由于重定向退化为队列请求,可以自行测试验证
*
* Notice! `withInQueue` is a concurrent redirection optimization
* The principle is that instead of receiving a packet as soon as a single request is,
* it is added to the request queue again and delayed to wait for the packet to recv.
* After all the original concurrent requests in the queue for the first time are recved, the redirect requests recv again.
* Otherwise, the concurrent request can be degraded to a queue request due to redirection, you can be tested and verified.
*/
parent::enqueue($request->withInQueue(true));
}
public function getMaxConcurrency(): int
{
return $this->max_concurrency;
}
public function withMaxConcurrency(int $num = -1): self
{
$this->max_concurrency = $num;
return $this;
}
/**
* @return ResponseMap|Response[]
*/
public function recv(): ResponseMap
{
$start_time = microtime(true);
$res_map = new ResponseMap(); //Result-set
$index = 0;
// FIXME: 并发模式使用的是defer机制, 这一机制可以节省协程的创建, 但当重定向发生时它无法完美地并发, 并且在执行时会和use_pool上限产生冲突, 导致死锁, 所以需要将其改成channel调度的模式
$max_co = $this->getMaxConcurrency();
if ($max_co > 0 && $max_co < $this->count()) {
if (!isset($this->concurrency_pool) || !$this->concurrency_pool->isEmpty()) {
$this->concurrency_pool = new SplQueue();
}
while (!$this->isEmpty()) {
$current_co = 0;
//de-queue from the total pool and en-queue to the controllable pool
while (!$this->isEmpty() && $max_co > $current_co++) {
/** @var $req Request */
$req = $this->dequeue();
$req->withSpecialMark($index++, 'requestQueueIndex');
if (!$req->isWaiting()) {
$req->exec();
} else {
throw new InvalidArgumentException("The waiting request is forbidden when using the max concurrency control!");
}
$this->concurrency_pool->enqueue($req);
}
while (!$this->concurrency_pool->isEmpty()) {
$req = $this->concurrency_pool->dequeue();
/** @var $req Request */
$res = $req->recv();
if ($res instanceof Request) {
$this->concurrency_pool->enqueue($res);
} else {
//response create
$res_map[$res->getSpecialMark('requestQueueIndex')] = $res;
if (($name = $req->getName()) && !isset($res_map[$name])) {
$res_map[$name] = &$res;
}
}
}
/** callback */
$is_finished = false;
$ret = $this->callInterceptor('after_concurrency', $res_map, $is_finished, $this);
if ($ret !== null) {
return $ret;
}
}
} else {
/**@var $req Request */
foreach ($this as $index => $req) {
$req->withSpecialMark($index, 'requestQueueIndex')->exec();
}
$req = null;
while (!$this->isEmpty()) {
$req = $this->dequeue();
/** @var $req Request */
$res = $req->recv();
if ($res instanceof Request) {
$this->enqueue($res);
} else {
//response create
$res_map[$res->getSpecialMark('requestQueueIndex')] = $res;
if (($name = $req->getName()) && !isset($res_map[$name])) {
$res_map[$name] = &$res;
}
// clear mark
$req->withInQueue(false);
}
}
}
$res_map->time = microtime(true) - $start_time;
/** callback */
$is_finished = true;
$ret = $this->callInterceptor('after_concurrency', $res_map, $is_finished, $this);
if ($ret !== null) {
return $ret;
}
return $res_map;
}
}