mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-29 18:27:44 +08:00
Merge branch 'master' into 2.1-merge
# Conflicts: # .travis.yml # composer.json # src/json-rpc/src/CoreMiddleware.php
This commit is contained in:
commit
11dabbf792
49
.github/workflows/test.yml
vendored
Normal file
49
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: PHPUnit for Hyperf
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
SW_VERSION: '4.5.6'
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: Test on PHP ${{ matrix.php-version }} MySQL ${{ matrix.mysql-version }}
|
||||
runs-on: "${{ matrix.os }}"
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php-version: ['7.2', '7.3', '7.4']
|
||||
mysql-version: ['5.7', '8.0']
|
||||
max-parallel: 6
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: phpize
|
||||
extensions: redis
|
||||
ini-values: extension=swoole, opcache.enable_cli=1
|
||||
coverage: none
|
||||
- name: Build Swoole
|
||||
run: ./.travis/swoole.install.sh
|
||||
- name: Setup Packages
|
||||
run: composer update -o
|
||||
- name: Setup Services
|
||||
run: |
|
||||
docker run --name mysql -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=true -d mysql:${{ matrix.mysql-version }} --bind-address=0.0.0.0 --default-authentication-plugin=mysql_native_password
|
||||
docker run --name redis -p 6379:6379 -d redis
|
||||
docker run -d --name dev-consul -e CONSUL_BIND_INTERFACE=eth0 -p 8500:8500 consul
|
||||
docker run --name nsq -p 4150:4150 -p 4151:4151 -p 4160:4160 -p 4161:4161 -p 4170:4170 -p 4171:4171 --entrypoint /bin/nsqd -d nsqio/nsq:latest
|
||||
docker build --tag grpc-server:latest src/grpc-client/tests/Mock
|
||||
docker run -d --name grpc-server -p 50051:50051 grpc-server:latest
|
||||
- name: Setup Mysql
|
||||
run: export TRAVIS_BUILD_DIR=$(pwd) && bash ./.travis/setup.mysql.sh
|
||||
- name: Run Scripts Before Test
|
||||
run: cp .travis/.env.example .env
|
||||
- name: Run Test Cases
|
||||
run: |
|
||||
composer analyse src
|
||||
composer test -- --exclude-group NonCoroutine
|
||||
vendor/bin/phpunit --group NonCoroutine
|
@ -5,13 +5,13 @@ sudo: required
|
||||
matrix:
|
||||
include:
|
||||
- php: 7.3
|
||||
env: SW_VERSION="4.5.5"
|
||||
env: SW_VERSION="4.5.6"
|
||||
- php: 7.3
|
||||
env: SW_VERSION="4.5.5" GUZZLE_7=1
|
||||
env: SW_VERSION="4.5.6" GUZZLE_7=1
|
||||
- php: 7.4
|
||||
env: SW_VERSION="4.5.5"
|
||||
env: SW_VERSION="4.5.6"
|
||||
- php: 7.4
|
||||
env: SW_VERSION="4.5.5" GUZZLE_7=1
|
||||
env: SW_VERSION="4.5.6" GUZZLE_7=1
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
@ -4,9 +4,9 @@ CURRENT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
TRAVIS_BUILD_DIR="${TRAVIS_BUILD_DIR:-$(dirname $(dirname $CURRENT_DIR))}"
|
||||
|
||||
echo -e "Create MySQL database..."
|
||||
mysql -u root -e "CREATE DATABASE IF NOT EXISTS hyperf charset=utf8mb4 collate=utf8mb4_unicode_ci;"
|
||||
cat "${TRAVIS_BUILD_DIR}/.travis/hyperf.sql" | mysql -u root hyperf
|
||||
mysql -h 127.0.0.1 -u root -e "CREATE DATABASE IF NOT EXISTS hyperf charset=utf8mb4 collate=utf8mb4_unicode_ci;"
|
||||
cat "${TRAVIS_BUILD_DIR}/.travis/hyperf.sql" | mysql -h 127.0.0.1 -u root hyperf
|
||||
|
||||
echo -e "Done\n"
|
||||
|
||||
wait
|
||||
wait
|
||||
|
@ -7,4 +7,4 @@ cd swoole
|
||||
phpize
|
||||
./configure --enable-openssl --enable-mysqlnd --enable-http2
|
||||
make -j$(nproc)
|
||||
make install
|
||||
sudo make install
|
||||
|
@ -1,4 +1,34 @@
|
||||
# v2.0.15 - TBD
|
||||
# v2.0.17 - TBD
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#2719](https://github.com/hyperf/hyperf/pull/2719) Fixed method `Arr::merge` does not works when `array1` does not constains the `$key`.
|
||||
|
||||
# v2.0.16 - 2020-10-26
|
||||
|
||||
## Added
|
||||
|
||||
- [#2682](https://github.com/hyperf/hyperf/pull/2682) Added method `getCacheTTL` for `CacheableInterface` which can control cache time each models.
|
||||
- [#2696](https://github.com/hyperf/hyperf/pull/2696) Added swoole tracker leak tool.
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#2680](https://github.com/hyperf/hyperf/pull/2680) Fixed Type error for `CastsValue`, because `$isSynchronized` don't have default value.
|
||||
- [#2680](https://github.com/hyperf/hyperf/pull/2680) Fixed default value in `$items` will be replaced by `__construct` for `CastsValue`.
|
||||
- [#2693](https://github.com/hyperf/hyperf/pull/2693) Fixed unexpected behavior in retry budget for `hyperf/retry`.
|
||||
- [#2695](https://github.com/hyperf/hyperf/pull/2695) Fixed method `Container::define()` does not works when the class has been resolved.
|
||||
|
||||
## Optimized
|
||||
|
||||
- [#2611](https://github.com/hyperf/hyperf/pull/2611) Optimized `FindDriver` for watcher, you can use it in alpine image.
|
||||
- [#2662](https://github.com/hyperf/hyperf/pull/2662) Optimized amqp consumer which can stop safely.
|
||||
- [#2690](https://github.com/hyperf/hyperf/pull/2690) Optimized `tracer` which ensure span finished and flushed.
|
||||
|
||||
# v2.0.15 - 2020-10-19
|
||||
|
||||
## Added
|
||||
|
||||
- [#2654](https://github.com/hyperf/hyperf/pull/2654) Added method `Hyperf\Utils\Resource::from` which can convert `string` to `resource`.
|
||||
|
||||
## Fixed
|
||||
|
||||
@ -6,6 +36,10 @@
|
||||
- [#2639](https://github.com/hyperf/hyperf/pull/2639) Fixed exception will not be normalized for json-rpc.
|
||||
- [#2643](https://github.com/hyperf/hyperf/pull/2643) Fixed undefined method unsearchable for `scout:flush`.
|
||||
|
||||
## Optimized
|
||||
|
||||
- [#2656](https://github.com/hyperf/hyperf/pull/2656) Optimized the response when parse parameters failed for json-rpc.
|
||||
|
||||
# v2.0.14 - 2020-10-12
|
||||
|
||||
## Added
|
||||
|
@ -68,7 +68,7 @@
|
||||
"php-amqplib/php-amqplib": "^2.7",
|
||||
"php-di/php-di": "^6.0",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpstan/phpstan": "0.12.50",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^9.4",
|
||||
"predis/predis": "^1.1",
|
||||
"reactivex/rxphp": "^2.0",
|
||||
|
BIN
docs/en/imgs/snowflake.jpeg
Normal file
BIN
docs/en/imgs/snowflake.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
157
docs/en/snowflake.md
Normal file
157
docs/en/snowflake.md
Normal file
@ -0,0 +1,157 @@
|
||||
# Snowflake
|
||||
|
||||
## Algorithm Introduction
|
||||
|
||||
`Snowflake` is a distributed global unique ID generation algorithm proposed by twitter. The result of the algorithm generating `ID` is a long integer with the size of `64bit` . Under the standard algorithm, its structure is shown in the figure below:
|
||||
|
||||
![snowflake](imgs/snowflake.jpeg)
|
||||
|
||||
-`One bit`, unused.
|
||||
- The highest bit in the binary system is the sign bit. The `ID` generated by us is generally a positive integer, so the highest bit is fixed to 0.
|
||||
|
||||
- `41 bits` to record the timestamp (MS).
|
||||
- `41 bits` can represent `2^41 - 1` numbers.
|
||||
- In other words, `41 bits` can represent the value of `2^41 - 1` milliseconds, and the unit year is `(2^41 - 1) / (1000 * 60 * 60 * 24 * 365)` about `69` years。
|
||||
|
||||
- `10 bits`, used to record the `ID` of the working machine.
|
||||
- It can be deployed in `2^10` nodes, including `5` bits `DatacenterId` and `5` bits `WorkerId`.
|
||||
|
||||
- `12 bits`, serial number, used to record different `id` generated in the same millisecond.
|
||||
- `12 bits` can represent the maximum number of positive integers `2^12 - 1` with a total of `4095` numbers, which represent the `4095` `ID` serial numbers generated by the same machine in the same time interval (MS).
|
||||
|
||||
`Snowflake` can guarantee that:
|
||||
|
||||
- All generated `ID` increase with time trend.
|
||||
- No duplicate `ID` will be generated in the whole distributed system (Because there is a distinction between `DatacenterId (5 bits)` and `WorkerId (5 bits)`.
|
||||
|
||||
The [hyperf/snowflake](https://github.com/hyperf/snowflake) component provides good extensibility in design, allowing you to implement other variant algorithms based on snowflake with simple extension.
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
composer require hyperf/snowflake
|
||||
```
|
||||
|
||||
## Use
|
||||
|
||||
The framework provides `MetaGeneratorInterface` and `IdGeneratorInterface`. `MetaGeneratorInterface` generates `Meta` files of `ID`, and `IdGeneratorInterface` generates `distributed ID` based on the corresponding `Meta` files.
|
||||
|
||||
The `MetaGeneratorInterface` used by the framework by default is a `millisecond level generator` based on `Redis`.
|
||||
The configuration file is located in `config/autoload/snowflake.php` If the configuration file does not exist, you can execute `php bin/hyperf.php vendor:publish hyperf/snowflake` command to create a default configuration. The contents of the configuration file are as follows:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Hyperf\Snowflake\MetaGenerator\RedisMilliSecondMetaGenerator;
|
||||
use Hyperf\Snowflake\MetaGenerator\RedisSecondMetaGenerator;
|
||||
use Hyperf\Snowflake\MetaGeneratorInterface;
|
||||
|
||||
return [
|
||||
'begin_second' => MetaGeneratorInterface::DEFAULT_BEGIN_SECOND,
|
||||
RedisMilliSecondMetaGenerator::class => [
|
||||
// Redis Pool
|
||||
'pool' => 'default',
|
||||
// To calculate the Key of WorkerId
|
||||
'key' => RedisMilliSecondMetaGenerator::DEFAULT_REDIS_KEY
|
||||
],
|
||||
RedisSecondMetaGenerator::class => [
|
||||
// Redis Pool
|
||||
'pool' => 'default',
|
||||
// To calculate the Key of WorkerId
|
||||
'key' => RedisMilliSecondMetaGenerator::DEFAULT_REDIS_KEY
|
||||
],
|
||||
];
|
||||
|
||||
```
|
||||
|
||||
Using `Snowflake` in the framework is very simple. You just need to take out the `IdGeneratorInterface` object from `DI`.
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Hyperf\Snowflake\IdGeneratorInterface;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
|
||||
$container = ApplicationContext::getContainer();
|
||||
$generator = $container->get(IdGeneratorInterface::class);
|
||||
|
||||
$id = $generator->generate();
|
||||
```
|
||||
|
||||
When you know that the `ID` needs to reverse the corresponding `Meta`, you just need to call `generate`.
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Hyperf\Snowflake\IdGeneratorInterface;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
|
||||
$container = ApplicationContext::getContainer();
|
||||
$generator = $container->get(IdGeneratorInterface::class);
|
||||
|
||||
$meta = $generator->degenerate($id);
|
||||
```
|
||||
|
||||
## Override 'Meta' generator
|
||||
|
||||
|
||||
There are many ways to implement the `distributed global unique ID`, and there are also many variants based on the `Snowflake` algorithm. Although they are all `Snowflake` algorithms, they are not the same. For example, someone may generate a `Meta` based on `UserId` rather than `WorkerId`. Next, let's implement a simple `MetaGenerator`.
|
||||
|
||||
In short, the `UserId` will definitely exceed '10 bits'. Therefore, the default `DataCenterId` and `WorkerId` cannot be installed. Therefore, the `UserId` module needs to be taken.
|
||||
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Hyperf\Snowflake\IdGenerator;
|
||||
|
||||
class UserDefinedIdGenerator
|
||||
{
|
||||
/**
|
||||
* @var IdGenerator\SnowflakeIdGenerator
|
||||
*/
|
||||
protected $idGenerator;
|
||||
|
||||
public function __construct(IdGenerator\SnowflakeIdGenerator $idGenerator)
|
||||
{
|
||||
$this->idGenerator = $idGenerator;
|
||||
}
|
||||
|
||||
public function generate(int $userId)
|
||||
{
|
||||
$meta = $this->idGenerator->getMetaGenerator()->generate();
|
||||
|
||||
return $this->idGenerator->generate($meta->setWorkerId($userId % 31));
|
||||
}
|
||||
|
||||
public function degenerate(int $id)
|
||||
{
|
||||
return $this->idGenerator->degenerate($id);
|
||||
}
|
||||
}
|
||||
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
|
||||
$container = ApplicationContext::getContainer();
|
||||
$generator = $container->get(UserDefinedIdGenerator::class);
|
||||
$userId = 20190620;
|
||||
|
||||
$id = $generator->generate($userId);
|
||||
|
||||
```
|
||||
|
||||
## Application in database modelon in database model
|
||||
|
||||
After configuring `Snowflake`, we can make the database model directly use `Snowflake` `ID` as the primary key.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class User extends \Hyperf\Database\Model\Model {
|
||||
use \Hyperf\Snowflake\Concern\Snowflake;
|
||||
}
|
||||
```
|
||||
|
||||
When the user model is created, the `Snowflake` algorithm will be used to generate the primary key by default.
|
@ -105,7 +105,9 @@ class AsyncQueueConsumer extends ConsumerProcess
|
||||
|
||||
这种模式会把对象直接序列化然后存到 `Redis` 等队列中,所以为了保证序列化后的体积,尽量不要将 `Container`,`Config` 等设置为成员变量。
|
||||
|
||||
比如以下 `Job` 的定义,是 **不可取** 的
|
||||
比如以下 `Job` 的定义,是 **不可取** 的,同理 `@Inject` 也是如此。
|
||||
|
||||
> 因为 Job 会被序列化,所以成员变量不要包含 匿名函数 等 无法被序列化 的内容,如果不清楚哪些内容无法被序列化,尽量使用注解方式。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
@ -1,5 +1,41 @@
|
||||
# 版本更新记录
|
||||
|
||||
# v2.0.16 - 2020-10-26
|
||||
|
||||
## 新增
|
||||
|
||||
- [#2682](https://github.com/hyperf/hyperf/pull/2682) 为 `CacheableInterface` 新增方法 `getCacheTTL` 可根据不同模型设置不同的缓存时间。
|
||||
- [#2696](https://github.com/hyperf/hyperf/pull/2696) 新增 Swoole Tracker 的内存检测工具。
|
||||
|
||||
## 修复
|
||||
|
||||
- [#2680](https://github.com/hyperf/hyperf/pull/2680) 修复 `CastsValue` 因为没有设置 `$isSynchronized` 默认值,导致的类型错误。
|
||||
- [#2680](https://github.com/hyperf/hyperf/pull/2680) 修复 `CastsValue` 中 `$items` 默认值会被 `__construct` 覆盖的问题。
|
||||
- [#2693](https://github.com/hyperf/hyperf/pull/2693) 修复 `hyperf/retry` 组件,`Budget` 表现不符合期望的问题。
|
||||
- [#2695](https://github.com/hyperf/hyperf/pull/2695) 修复方法 `Container::define()` 因为容器中的对象已被实例化,而无法重定义的问题。
|
||||
|
||||
## 优化
|
||||
|
||||
- [#2611](https://github.com/hyperf/hyperf/pull/2611) 优化 `hyperf/watcher` 组件 `FindDriver` ,使其可以在 `Alpine` 镜像中使用。
|
||||
- [#2662](https://github.com/hyperf/hyperf/pull/2662) 优化 `Amqp` 消费者进程,使其可以配合 `Signal` 组件安全停止。
|
||||
- [#2690](https://github.com/hyperf/hyperf/pull/2690) 优化 `hyperf/tracer` 组件,确保其可以正常执行 `finish` 和 `flush` 方法。
|
||||
|
||||
# v2.0.15 - 2020-10-19
|
||||
|
||||
## 新增
|
||||
|
||||
- [#2654](https://github.com/hyperf/hyperf/pull/2654) 新增方法 `Hyperf\Utils\Resource::from`,可以方便的将 `string` 转化为 `resource`。
|
||||
|
||||
## 修复
|
||||
|
||||
- [#2634](https://github.com/hyperf/hyperf/pull/2634) [#2640](https://github.com/hyperf/hyperf/pull/2640) 修复 `snowflake` 组件中,元数据生成器 `RedisSecondMetaGenerator` 会产生相同元数据的问题。
|
||||
- [#2639](https://github.com/hyperf/hyperf/pull/2639) 修复 `json-rpc` 组件中,异常无法正常被序列化的问题。
|
||||
- [#2643](https://github.com/hyperf/hyperf/pull/2643) 修复 `scout:flush` 执行失败的问题。
|
||||
|
||||
## 优化
|
||||
|
||||
- [#2656](https://github.com/hyperf/hyperf/pull/2656) 优化了 `json-rpc` 组件中,参数解析失败后,也可以返回对应的错误信息。
|
||||
|
||||
# v2.0.14 - 2020-10-12
|
||||
|
||||
## 新增
|
||||
|
@ -79,7 +79,7 @@ Swoole 协程也是对异步回调的一种解决方案,在 `PHP` 语言下,
|
||||
|
||||
### 最大协程数限制
|
||||
|
||||
对 `Swoole Server` 通过 `set` 方法设置 `max_coroutine` 参数,用于配置一个 `Worker` 进程最多可存在的协程数量。因为随着 `Worker` 进程处理的协程数目的增加,其对应占用的内存也会随之增加,为了避免超出 `PHP` 的 `memory_limit` 限制,请根据实际业务的压测结果设置该值,`Swoole` 的默认值为 `3000`, 在 `hyperf-skeleton` 项目中默认设置为 `100000`。
|
||||
对 `Swoole Server` 通过 `set` 方法设置 `max_coroutine` 参数,用于配置一个 `Worker` 进程最多可存在的协程数量。因为随着 `Worker` 进程处理的协程数目的增加,其对应占用的内存也会随之增加,为了避免超出 `PHP` 的 `memory_limit` 限制,请根据实际业务的压测结果设置该值,`Swoole` 的默认值为 `100000`( `Swoole` 版本小于 `v4.4.0-beta` 时默认值为 `3000` ), 在 `hyperf-skeleton` 项目中默认设置为 `100000`。
|
||||
|
||||
## 使用协程
|
||||
|
||||
|
@ -147,6 +147,26 @@ User::query(true)->where('gender', '>', 1)->delete();
|
||||
|
||||
对于这种情况,我们可以修改 `use_default_value` 为 `true`,并添加 `Hyperf\DbConnection\Listener\InitTableCollectorListener` 到 `listener.php` 配置中,使 Hyperf 应用在启动时主动去获取数据库的字段信息,并在获取缓存数据时与之比较并进行缓存数据修正。
|
||||
|
||||
### 控制模型中缓存时间
|
||||
|
||||
除了 `database.php` 中配置的默认缓存时间 `ttl` 外,`Hyperf\ModelCache\Cacheable` 支持对模型配置更细的缓存时间:
|
||||
|
||||
```php
|
||||
class User extends Model implements CacheableInterface
|
||||
{
|
||||
use Cacheable;
|
||||
|
||||
/**
|
||||
* 缓存 10 分钟,返回 null 则使用配置文件中设置的超时时间
|
||||
* @return int|null
|
||||
*/
|
||||
public function getCacheTTL(): ?int
|
||||
{
|
||||
return 600;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### EagerLoad
|
||||
|
||||
当我们使用模型关系时,可以通过 `load` 解决 `N+1` 的问题,但仍然需要查一次数据库。模型缓存通过重写了 `ModelBuilder`,可以让用户尽可能的从缓存中拿到对应的模型。
|
||||
|
@ -20,7 +20,7 @@ composer require hyperf/elasticsearch
|
||||
Scout 安装完成后,使用 vendor:publish 命令来生成 Scout 配置文件。这个命令将在你的 config 目录下生成一个 scout.php 配置文件。
|
||||
|
||||
```bash
|
||||
php bin/hyperf vendor:publish hyperf/scout
|
||||
php bin/hyperf.php vendor:publish hyperf/scout
|
||||
```
|
||||
|
||||
最后,在你要做搜索的模型中添加 Hyperf\Scout\Searchable trait。这个 trait 会注册一个模型观察者来保持模型和所有驱动的同步:
|
||||
|
@ -180,3 +180,67 @@ return [
|
||||
Hyperf\SwooleTracker\Aspect\CoroutineHandlerAspect::class,
|
||||
];
|
||||
```
|
||||
|
||||
## 免费内存泄漏检测工具
|
||||
|
||||
Swoole Tracker 本是一款商业产品,拥有进行内存泄漏检测的能力,不过 Swoole Tracker 把内存泄漏检测的功能完全免费给 PHP 社区使用,完善 PHP 生态,回馈社区,下面将概述它的具体用法。
|
||||
|
||||
1. 前往 [Swoole Tracker 官网](https://business.swoole.com/SwooleTracker/download/) 下载最新的 Swoole Tracker 扩展;
|
||||
|
||||
2. 和上文添加扩展相同,再加入一行配置:
|
||||
|
||||
```ini
|
||||
;Leak检测开关
|
||||
apm.enable_malloc_hook=1
|
||||
```
|
||||
|
||||
!> 注意:不要在composer安装依赖时开启;不要在生成代理类缓存时开启。
|
||||
|
||||
3. 根据自己的业务,在 Swoole 的 onReceive 或者 onRequest 事件开头加上 `trackerHookMalloc()` 调用:
|
||||
|
||||
```php
|
||||
$http->on('request', function ($request, $response) {
|
||||
trackerHookMalloc();
|
||||
$response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
|
||||
});
|
||||
```
|
||||
|
||||
每次调用结束后(第一次调用不会被记录),都会生成一个泄漏的信息到 `/tmp/trackerleak` 日志中,我们可以在 Cli 命令行调用 `trackerAnalyzeLeak()` 函数即可分析泄漏日志,生成泄漏报告
|
||||
|
||||
```shell
|
||||
php -r "trackerAnalyzeLeak();"
|
||||
```
|
||||
|
||||
下面是泄漏报告的格式:
|
||||
|
||||
没有内存泄漏的情况:
|
||||
|
||||
```
|
||||
[16916 (Loop 5)] ✅ Nice!! No Leak Were Detected In This Loop
|
||||
```
|
||||
|
||||
其中`16916`表示进程 id,`Loop 5`表示第 5 次调用主函数生成的泄漏信息
|
||||
|
||||
有确定的内存泄漏:
|
||||
|
||||
```
|
||||
[24265 (Loop 8)] /tests/mem_leak/http_server.php:125 => [12928]
|
||||
[24265 (Loop 8)] /tests/mem_leak/http_server.php:129 => [12928]
|
||||
[24265 (Loop 8)] ❌ This Loop TotalLeak: [25216]
|
||||
```
|
||||
|
||||
表示第 8 次调用`http_server.php`的 125 行和 129 行,分别泄漏了 12928 字节内存,总共泄漏了 25216 字节内存。
|
||||
|
||||
通过调用 `trackerCleanLeak()` 可以清除泄漏日志,重新开始。[了解更多内存检测工具使用细节](https://www.kancloud.cn/swoole-inc/ee-help-wiki/1941569)
|
||||
|
||||
在 Hyperf 中如果需要检测 HTTP Server 中的内存泄漏,可以在 `config/autoload/aspects.php` 配置以下 `Aspect`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
Hyperf\SwooleTracker\Aspect\OnRequestAspect::class,
|
||||
];
|
||||
```
|
||||
|
||||
其他 Server 可以参照此 `Aspect` 进行重写使用。
|
||||
|
@ -47,3 +47,4 @@ parameters:
|
||||
- '#Function getSwooleTracker.* not found#'
|
||||
- '#InfluxDB\\Point constructor expects float\|null, string given#'
|
||||
- '#Call to an undefined method Hyperf\\Database\\Model\\Builder::unsearchable#'
|
||||
- '#trackerHookMalloc not found#'
|
||||
|
@ -21,6 +21,7 @@ use Hyperf\Amqp\Message\MessageInterface;
|
||||
use Hyperf\Amqp\Pool\PoolFactory;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
|
||||
use Hyperf\Process\ProcessManager;
|
||||
use Hyperf\Utils\Coroutine\Concurrent;
|
||||
use PhpAmqpLib\Channel\AMQPChannel;
|
||||
use PhpAmqpLib\Message\AMQPMessage;
|
||||
@ -92,7 +93,7 @@ class Consumer extends Builder
|
||||
);
|
||||
|
||||
try {
|
||||
while ($channel->is_consuming()) {
|
||||
while ($channel->is_consuming() && ProcessManager::isRunning()) {
|
||||
$channel->wait();
|
||||
}
|
||||
} catch (MaxConsumptionException $ex) {
|
||||
|
@ -24,30 +24,39 @@ abstract class CastsValue implements Synchronized, Arrayable
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $items;
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isSynchronized;
|
||||
protected $isSynchronized = false;
|
||||
|
||||
public function __construct(Model $model, $itmes = [])
|
||||
public function __construct(Model $model, $items = [])
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->items = $itmes;
|
||||
$this->items = array_merge($this->items, $items);
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->items[$name];
|
||||
return $this->items[$name] ?? null;
|
||||
}
|
||||
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$this->items[$name] = $value;
|
||||
$this->isSynchronized = false;
|
||||
$this->model->syncAttributes();
|
||||
$this->isSynchronized = true;
|
||||
$this->syncAttributes();
|
||||
}
|
||||
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($this->items[$name]);
|
||||
}
|
||||
|
||||
public function __unset($name)
|
||||
{
|
||||
unset($this->items[$name]);
|
||||
$this->syncAttributes();
|
||||
}
|
||||
|
||||
public function isSynchronized(): bool
|
||||
@ -59,4 +68,11 @@ abstract class CastsValue implements Synchronized, Arrayable
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function syncAttributes(): void
|
||||
{
|
||||
$this->isSynchronized = false;
|
||||
$this->model->syncAttributes();
|
||||
$this->isSynchronized = true;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class DatabaseMigratorIntegrationTest extends TestCase
|
||||
|
||||
$dbConfig = [
|
||||
'driver' => 'mysql',
|
||||
'host' => 'localhost',
|
||||
'host' => '127.0.0.1',
|
||||
'database' => 'hyperf',
|
||||
'username' => 'root',
|
||||
'password' => '',
|
||||
|
@ -28,6 +28,8 @@ class DatabaseModelCustomCastingTest extends TestCase
|
||||
protected function tearDown(): void
|
||||
{
|
||||
\Mockery::close();
|
||||
UserInfoCaster::$setCount = 0;
|
||||
UserInfoCaster::$getCount = 0;
|
||||
}
|
||||
|
||||
public function testBasicCustomCasting()
|
||||
@ -215,16 +217,32 @@ class DatabaseModelCustomCastingTest extends TestCase
|
||||
$model->syncOriginal();
|
||||
|
||||
$attributes = $model->getAttributes();
|
||||
$this->assertSame(['name' => 'Hyperf', 'gender' => 1], $attributes);
|
||||
$this->assertSame(['name' => 'Hyperf', 'gender' => 1], Arr::only($attributes, ['name', 'gender']));
|
||||
|
||||
$user->name = 'Nano';
|
||||
$attributes = $model->getAttributes();
|
||||
$this->assertSame(['name' => 'Nano', 'gender' => 1], $attributes);
|
||||
$this->assertSame(['name' => 'Nano', 'gender' => 1], Arr::only($attributes, ['name', 'gender']));
|
||||
|
||||
$this->assertSame(['name' => 'Nano'], $model->getDirty());
|
||||
$this->assertSame(2, UserInfoCaster::$setCount);
|
||||
$this->assertSame(0, UserInfoCaster::$getCount);
|
||||
}
|
||||
|
||||
public function testCastsValueSupportNull()
|
||||
{
|
||||
$model = new TestModelWithCustomCast();
|
||||
$model->user = $user = new UserInfo($model, ['name' => 'Hyperf', 'gender' => 1]);
|
||||
$attributes = $model->getAttributes();
|
||||
$this->assertSame(['name' => 'Hyperf', 'gender' => 1, 'role_id' => 0], $attributes);
|
||||
$this->assertSame(0, $user->role_id);
|
||||
$user->role_id = 1;
|
||||
$this->assertSame(['name' => 'Hyperf', 'gender' => 1, 'role_id' => 1], $model->getAttributes());
|
||||
unset($user->role_id);
|
||||
$this->assertSame(['name' => 'Hyperf', 'gender' => 1, 'role_id' => null], $model->getAttributes());
|
||||
$this->assertSame(null, $user->role_id);
|
||||
unset($user->not_found);
|
||||
$this->assertSame(['name' => 'Hyperf', 'gender' => 1, 'role_id' => null], $model->getAttributes());
|
||||
}
|
||||
}
|
||||
|
||||
class TestModelWithCustomCast extends Model
|
||||
@ -325,6 +343,7 @@ class UserInfoCaster implements CastsAttributes
|
||||
return [
|
||||
'name' => $value->name,
|
||||
'gender' => $value->gender,
|
||||
'role_id' => $value->role_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -416,7 +435,11 @@ class Address
|
||||
/**
|
||||
* @property string $name
|
||||
* @property int $gender
|
||||
* @property null|int $role_id
|
||||
*/
|
||||
class UserInfo extends CastsValue
|
||||
{
|
||||
protected $items = [
|
||||
'role_id' => 0,
|
||||
];
|
||||
}
|
||||
|
@ -1882,7 +1882,7 @@ class ModelTest extends TestCase
|
||||
|
||||
$dbConfig = [
|
||||
'driver' => 'mysql',
|
||||
'host' => 'localhost',
|
||||
'host' => '127.0.0.1',
|
||||
'database' => 'hyperf',
|
||||
'username' => 'root',
|
||||
'password' => '',
|
||||
|
@ -47,6 +47,7 @@ abstract class AbstractTestCase extends TestCase
|
||||
'db' => [
|
||||
'default' => [
|
||||
'driver' => $this->driver,
|
||||
'host' => '127.0.0.1',
|
||||
'password' => '',
|
||||
'database' => 'hyperf',
|
||||
'pool' => [
|
||||
@ -56,6 +57,7 @@ abstract class AbstractTestCase extends TestCase
|
||||
],
|
||||
'pdo' => [
|
||||
'driver' => 'pdo',
|
||||
'host' => '127.0.0.1',
|
||||
'password' => '',
|
||||
'database' => 'hyperf',
|
||||
'pool' => [
|
||||
|
@ -100,7 +100,7 @@ class Container implements HyperfContainerInterface
|
||||
*/
|
||||
public function define(string $name, $definition)
|
||||
{
|
||||
$this->definitionSource->addDefinition($name, $definition);
|
||||
$this->setDefinition($name, $definition);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,7 +153,10 @@ class Container implements HyperfContainerInterface
|
||||
return $this->definitionSource;
|
||||
}
|
||||
|
||||
protected function setDefinition(string $name, DefinitionInterface $definition): void
|
||||
/**
|
||||
* @param array|callable|string $definition
|
||||
*/
|
||||
private function setDefinition(string $name, $definition): void
|
||||
{
|
||||
// Clear existing entry if it exists
|
||||
if (array_key_exists($name, $this->resolvedEntries)) {
|
||||
|
@ -13,8 +13,10 @@ namespace HyperfTest\Di;
|
||||
|
||||
use Hyperf\Di\Container;
|
||||
use Hyperf\Di\Definition\DefinitionSource;
|
||||
use HyperfTest\Di\Stub\Bar;
|
||||
use HyperfTest\Di\Stub\Foo;
|
||||
use HyperfTest\Di\Stub\FooInterface;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
@ -23,6 +25,11 @@ use PHPUnit\Framework\TestCase;
|
||||
*/
|
||||
class ContainerTest extends TestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
public function testHas()
|
||||
{
|
||||
$container = new Container(new DefinitionSource([]));
|
||||
@ -44,5 +51,10 @@ class ContainerTest extends TestCase
|
||||
$container = new Container(new DefinitionSource([]));
|
||||
$container->define(FooInterface::class, Foo::class);
|
||||
$this->assertInstanceOf(Foo::class, $container->make(FooInterface::class));
|
||||
|
||||
$container->define(FooInterface::class, function () {
|
||||
return Mockery::mock(Bar::class);
|
||||
});
|
||||
$this->assertInstanceOf(Bar::class, $foo = $container->make(FooInterface::class));
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ class InjectTest extends TestCase
|
||||
public function testInjectEmptyVar()
|
||||
{
|
||||
$this->expectException(AnnotationException::class);
|
||||
$this->expectExceptionMessage('The @Inject value is invalid for HyperfTest\Di\Stub\EmptyVarValue->demo. Because Argument 1 passed to Roave\BetterReflection\TypesFinder\FindPropertyType::Roave\BetterReflection\TypesFinder\{closure}() must be an instance of phpDocumentor\Reflection\DocBlock\Tags\Var_, instance of phpDocumentor\Reflection\DocBlock\Tags\InvalidTag given');
|
||||
$this->expectExceptionMessage('The @Inject value is invalid for HyperfTest\Di\Stub\EmptyVarValue->demo');
|
||||
|
||||
BetterReflectionManager::initClassReflector([__DIR__ . '/Stub']);
|
||||
|
||||
|
@ -14,6 +14,8 @@ namespace HyperfTest\GrpcClient;
|
||||
use Grpc\Info;
|
||||
use Hyperf\Grpc\Parser;
|
||||
use Hyperf\GrpcClient\Request;
|
||||
use Hyperf\Utils\Composer;
|
||||
use Jean85\PrettyVersions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
@ -22,6 +24,14 @@ use PHPUnit\Framework\TestCase;
|
||||
*/
|
||||
class RequestTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
$json = Composer::getLockContent();
|
||||
if (version_compare($json['plugin-api-version'], '2.0.0', '>=')) {
|
||||
$this->markTestSkipped(PrettyVersions::class . ' does not support composer v2.0');
|
||||
}
|
||||
}
|
||||
|
||||
public function testRequest()
|
||||
{
|
||||
$request = new Request($path = 'grpc.service/path', $info = new Info());
|
||||
|
@ -42,7 +42,13 @@ class CoreMiddleware extends \Hyperf\RpcServer\CoreMiddleware
|
||||
// Route found, but the handler does not exist.
|
||||
return $this->responseBuilder->buildErrorResponse($request, ResponseBuilder::INTERNAL_ERROR);
|
||||
}
|
||||
$parameters = $this->parseMethodParameters($controller, $action, $request->getParsedBody());
|
||||
|
||||
try {
|
||||
$parameters = $this->parseMethodParameters($controller, $action, $request->getParsedBody());
|
||||
} catch (\InvalidArgumentException $exception) {
|
||||
return $this->responseBuilder->buildErrorResponse($request, ResponseBuilder::INVALID_PARAMS);
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $controllerInstance->{$action}(...$parameters);
|
||||
} catch (\Throwable $exception) {
|
||||
|
@ -60,6 +60,14 @@ trait Cacheable
|
||||
return $manager->destroy([$this->getKey()], get_called_class());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expire time for cache.
|
||||
*/
|
||||
public function getCacheTTL(): ?int
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment a column's value by a given amount.
|
||||
* @param string $column
|
||||
|
@ -21,4 +21,6 @@ interface CacheableInterface
|
||||
public static function findManyFromCache(array $ids): Collection;
|
||||
|
||||
public function deleteCache(): bool;
|
||||
|
||||
public function getCacheTTL(): ?int;
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ class Manager
|
||||
if (is_null($data)) {
|
||||
$model = $instance->newQuery()->where($primaryKey, '=', $id)->first();
|
||||
if ($model) {
|
||||
$ttl = $handler->getConfig()->getTtl();
|
||||
$ttl = $this->getCacheTTL($instance, $handler);
|
||||
$handler->set($key, $this->formatModel($model), $ttl);
|
||||
} else {
|
||||
$ttl = $handler->getConfig()->getEmptyModelTtl();
|
||||
@ -141,7 +141,7 @@ class Manager
|
||||
$targetIds = array_diff($ids, $fetchIds);
|
||||
if ($targetIds) {
|
||||
$models = $instance->newQuery()->whereIn($primaryKey, $targetIds)->get();
|
||||
$ttl = $handler->getConfig()->getTtl();
|
||||
$ttl = $this->getCacheTTL($instance, $handler);
|
||||
/** @var Model $model */
|
||||
foreach ($models as $model) {
|
||||
$id = $model->getKey();
|
||||
@ -217,6 +217,17 @@ class Manager
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateInterval|int
|
||||
*/
|
||||
protected function getCacheTTL(Model $instance, HandlerInterface $handler)
|
||||
{
|
||||
if ($instance instanceof CacheableInterface) {
|
||||
return $instance->getCacheTTL() ?? $handler->getConfig()->getTtl();
|
||||
}
|
||||
return $handler->getConfig()->getTtl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string $id
|
||||
*/
|
||||
|
@ -15,6 +15,8 @@ use Hyperf\Config\Config;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\DbConnection\Collector\TableCollector;
|
||||
use Hyperf\ModelCache;
|
||||
use Hyperf\ModelCache\Handler\HandlerInterface;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use HyperfTest\ModelCache\Stub\ManagerStub;
|
||||
use HyperfTest\ModelCache\Stub\ModelStub;
|
||||
@ -60,6 +62,37 @@ class ManagerTest extends TestCase
|
||||
$this->assertSame(['id' => 1, 'json_data' => json_encode($json), 'str' => null, 'float_num' => 0.1], $res);
|
||||
}
|
||||
|
||||
public function testGetCacheTTL()
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
$container->shouldReceive('get')->once()->with(StdoutLoggerInterface::class)->andReturn(new StdoutLogger());
|
||||
$container->shouldReceive('get')->once()->with(ConfigInterface::class)->andReturn(new Config($this->getConfig()));
|
||||
$container->shouldReceive('make')->with(ContainerInterface::class)->andReturn($container);
|
||||
$container->shouldReceive('get')->with(EventDispatcherInterface::class)->andReturn(null);
|
||||
$container->shouldReceive('get')->with(TableCollector::class)->andReturn(new TableCollector());
|
||||
|
||||
ApplicationContext::setContainer($container);
|
||||
$handler = Mockery::mock(HandlerInterface::class);
|
||||
$handler->shouldReceive('getConfig')->andReturnUsing(function () {
|
||||
return new ModelCache\Config([
|
||||
'ttl' => 1000,
|
||||
], 'default');
|
||||
});
|
||||
$manager = new ManagerStub($container);
|
||||
|
||||
$model = new ModelStub();
|
||||
$this->assertSame(1000, $manager->getCacheTTL($model, $handler));
|
||||
$model = new class() extends ModelStub implements ModelCache\CacheableInterface {
|
||||
use ModelCache\Cacheable;
|
||||
|
||||
public function getCacheTTL(): ?int
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
};
|
||||
$this->assertSame(100, $manager->getCacheTTL($model, $handler));
|
||||
}
|
||||
|
||||
protected function getConfig(): array
|
||||
{
|
||||
return [
|
||||
|
@ -368,4 +368,17 @@ class ModelCacheTest extends TestCase
|
||||
|
||||
$model->delete();
|
||||
}
|
||||
|
||||
public function testModelCacheTTL()
|
||||
{
|
||||
$container = ContainerStub::mockContainer();
|
||||
$model = new BookModel();
|
||||
$this->assertSame(100, $model->getCacheTTL());
|
||||
|
||||
/** @var \Redis $redis */
|
||||
$redis = $container->make(RedisProxy::class, ['pool' => 'default']);
|
||||
$redis->del('{mc:default:m:book}:id:1');
|
||||
BookModel::findFromCache(1);
|
||||
$this->assertSame(100, $redis->ttl('{mc:default:m:book}:id:1'));
|
||||
}
|
||||
}
|
||||
|
@ -56,4 +56,9 @@ class BookModel extends Model implements CacheableInterface
|
||||
{
|
||||
return $this->morphOne(ImageModel::class, 'imageable');
|
||||
}
|
||||
|
||||
public function getCacheTTL(): ?int
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ class ContainerStub
|
||||
],
|
||||
'redis' => [
|
||||
'default' => [
|
||||
'host' => 'localhost',
|
||||
'host' => '127.0.0.1',
|
||||
'auth' => null,
|
||||
'port' => 6379,
|
||||
'db' => 0,
|
||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace HyperfTest\ModelCache\Stub;
|
||||
|
||||
use Hyperf\Database\Model\Model;
|
||||
use Hyperf\ModelCache\Handler\HandlerInterface;
|
||||
|
||||
class ManagerStub extends \Hyperf\ModelCache\Manager
|
||||
{
|
||||
@ -19,4 +20,9 @@ class ManagerStub extends \Hyperf\ModelCache\Manager
|
||||
{
|
||||
return parent::formatModel($model);
|
||||
}
|
||||
|
||||
public function getCacheTTL(Model $instance, HandlerInterface $handler)
|
||||
{
|
||||
return parent::getCacheTTL($instance, $handler);
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class ContainerStub
|
||||
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn(new Config([
|
||||
'redis' => [
|
||||
'default' => [
|
||||
'host' => 'localhost',
|
||||
'host' => '127.0.0.1',
|
||||
'auth' => null,
|
||||
'port' => 6379,
|
||||
'db' => 0,
|
||||
|
@ -42,11 +42,17 @@ class RetryBudget implements RetryBudgetInterface
|
||||
*/
|
||||
private $timerId;
|
||||
|
||||
/**
|
||||
* @var float|int
|
||||
*/
|
||||
private $maxToken;
|
||||
|
||||
public function __construct(int $ttl, int $minRetriesPerSec, float $percentCanRetry)
|
||||
{
|
||||
$this->ttl = $ttl;
|
||||
$this->minRetriesPerSec = $minRetriesPerSec;
|
||||
$this->percentCanRetry = $percentCanRetry;
|
||||
$this->maxToken = ($this->minRetriesPerSec / $this->percentCanRetry) * $this->ttl;
|
||||
$this->budget = new SplQueue();
|
||||
}
|
||||
|
||||
@ -70,9 +76,7 @@ class RetryBudget implements RetryBudgetInterface
|
||||
for ($i = 0; $i < $this->minRetriesPerSec / $this->percentCanRetry; ++$i) {
|
||||
$this->produce();
|
||||
}
|
||||
while (! $this->budget->isEmpty()
|
||||
&& $this->budget->top() <= microtime(true)
|
||||
) {
|
||||
while ($this->hasOverflown()) {
|
||||
$this->budget->dequeue();
|
||||
}
|
||||
});
|
||||
@ -98,4 +102,10 @@ class RetryBudget implements RetryBudgetInterface
|
||||
$t = microtime(true) + $this->ttl;
|
||||
$this->budget->push($t);
|
||||
}
|
||||
|
||||
public function hasOverflown(): bool
|
||||
{
|
||||
return (! $this->budget->isEmpty() && $this->budget->bottom() <= microtime(true))
|
||||
|| $this->budget->count() > $this->maxToken;
|
||||
}
|
||||
}
|
||||
|
@ -68,5 +68,20 @@ class RetryBudgetTest extends TestCase
|
||||
$this->assertTrue($budget->consume());
|
||||
$this->assertTrue($budget->consume());
|
||||
$this->assertTrue(! $budget->consume());
|
||||
|
||||
// Retry budget should never have more than 1 token in this test
|
||||
$budget = new RetryBudget(
|
||||
1,
|
||||
1,
|
||||
1
|
||||
);
|
||||
$budget->init();
|
||||
$ref = new \ReflectionClass(RetryBudget::class);
|
||||
$prop = $ref->getProperty('budget');
|
||||
$prop->setAccessible(true);
|
||||
System::sleep(1.2);
|
||||
$this->assertLessThanOrEqual(1, $prop->getValue($budget)->count());
|
||||
System::sleep(1.2);
|
||||
$this->assertLessThanOrEqual(1, $prop->getValue($budget)->count());
|
||||
}
|
||||
}
|
||||
|
43
src/swoole-tracker/src/Aspect/OnRequestAspect.php
Normal file
43
src/swoole-tracker/src/Aspect/OnRequestAspect.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\SwooleTracker\Aspect;
|
||||
|
||||
use Hyperf\Di\Aop\AbstractAspect;
|
||||
use Hyperf\Di\Aop\ProceedingJoinPoint;
|
||||
use Hyperf\HttpServer\Server;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use function trackerHookMalloc;
|
||||
|
||||
class OnRequestAspect extends AbstractAspect
|
||||
{
|
||||
public $classes = [
|
||||
Server::class . '::onRequest',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function process(ProceedingJoinPoint $proceedingJoinPoint)
|
||||
{
|
||||
if (function_exists('trackerHookMalloc')) {
|
||||
trackerHookMalloc();
|
||||
}
|
||||
return $proceedingJoinPoint->process();
|
||||
}
|
||||
}
|
@ -43,12 +43,15 @@ class TraceMiddleware implements MiddlewareInterface
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$span = $this->buildSpan($request);
|
||||
$response = $handler->handle($request);
|
||||
$span->finish();
|
||||
|
||||
defer(function () {
|
||||
$this->tracer->flush();
|
||||
});
|
||||
try {
|
||||
$response = $handler->handle($request);
|
||||
} finally {
|
||||
$span->finish();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
@ -516,7 +516,7 @@ class Arr
|
||||
if ($isAssoc) {
|
||||
foreach ($array2 as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$array1[$key] = static::merge($array1[$key], $value, $unique);
|
||||
$array1[$key] = static::merge($array1[$key] ?? [], $value, $unique);
|
||||
} else {
|
||||
$array1[$key] = $value;
|
||||
}
|
||||
|
35
src/utils/src/Resource.php
Normal file
35
src/utils/src/Resource.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Utils;
|
||||
|
||||
class Resource
|
||||
{
|
||||
/**
|
||||
* TODO: Swoole file hook does not support `php://temp` and `php://memory`.
|
||||
* @return false|resource
|
||||
*/
|
||||
public static function from(string $body, string $filename = 'php://temp')
|
||||
{
|
||||
$resource = fopen($filename, 'r+');
|
||||
if ($body !== '') {
|
||||
fwrite($resource, $body);
|
||||
fseek($resource, 0);
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
public static function fromMemory(string $body)
|
||||
{
|
||||
return static::from($body, 'php://memory');
|
||||
}
|
||||
}
|
@ -73,6 +73,8 @@ class ArrTest extends TestCase
|
||||
$this->assertSame(['id' => 1, 'ids' => [1, 2, 3], 'name' => 'Hyperf'], Arr::merge(['id' => 1, 'ids' => [1, 2]], ['name' => 'Hyperf', 'ids' => [1, 2, 3]]));
|
||||
$this->assertSame(['id' => 1, 'ids' => [1, 2, 1, 2, 3], 'name' => 'Hyperf'], Arr::merge(['id' => 1, 'ids' => [1, 2]], ['name' => 'Hyperf', 'ids' => [1, 2, 3]], false));
|
||||
|
||||
$this->assertSame(['id' => 1, 'name' => ['Hyperf']], Arr::merge(['id' => 2], ['id' => 1, 'name' => ['Hyperf']]));
|
||||
|
||||
$array1 = [
|
||||
'logger' => [
|
||||
'default' => [
|
||||
|
45
src/utils/tests/ResourceTest.php
Normal file
45
src/utils/tests/ResourceTest.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace HyperfTest\Utils;
|
||||
|
||||
use Hyperf\Utils\Resource;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class ResourceTest extends TestCase
|
||||
{
|
||||
public function testFrom()
|
||||
{
|
||||
$data = '123123';
|
||||
$resource = Resource::from($data);
|
||||
$this->assertSame('1', fread($resource, 1));
|
||||
$this->assertSame('23', fread($resource, 2));
|
||||
$this->assertSame('123', fread($resource, 10));
|
||||
}
|
||||
|
||||
public function testFromMemoryLeak()
|
||||
{
|
||||
$data = str_repeat('1', 1024 * 1024);
|
||||
$memory = memory_get_usage(true);
|
||||
for ($i = 0; $i < 100; ++$i) {
|
||||
Resource::fromMemory($data);
|
||||
$current = memory_get_usage(true);
|
||||
$leak = $current - $memory;
|
||||
$memory = $current;
|
||||
}
|
||||
|
||||
$this->assertSame(0, $leak);
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ class ValidationExistsRuleTest extends TestCase
|
||||
$connector = new ConnectionFactory($container);
|
||||
$dbConfig = [
|
||||
'driver' => 'mysql',
|
||||
'host' => 'localhost',
|
||||
'host' => '127.0.0.1',
|
||||
'database' => 'hyperf',
|
||||
'username' => 'root',
|
||||
'password' => '',
|
||||
|
@ -27,12 +27,26 @@ class FindDriver implements DriverInterface
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isDarwin;
|
||||
protected $isDarwin = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isSupportFloatMinutes = true;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $startTime;
|
||||
|
||||
public function __construct(Option $option)
|
||||
{
|
||||
$this->option = $option;
|
||||
$this->isDarwin = PHP_OS === 'Darwin';
|
||||
if (PHP_OS === 'Darwin') {
|
||||
$this->isDarwin = true;
|
||||
} else {
|
||||
$this->isDarwin = false;
|
||||
}
|
||||
if ($this->isDarwin) {
|
||||
$ret = System::exec('which gfind');
|
||||
if (empty($ret['output'])) {
|
||||
@ -43,21 +57,27 @@ class FindDriver implements DriverInterface
|
||||
if (empty($ret['output'])) {
|
||||
throw new \InvalidArgumentException('find not exists.');
|
||||
}
|
||||
$ret = System::exec('find --help', true);
|
||||
$this->isSupportFloatMinutes = (strpos($ret['output'] ?? '', 'BusyBox')) === false;
|
||||
}
|
||||
}
|
||||
|
||||
public function watch(Channel $channel): void
|
||||
{
|
||||
$this->startTime = time();
|
||||
$ms = $this->option->getScanInterval();
|
||||
Timer::tick($ms, function () use ($channel, $ms) {
|
||||
$seconds = ceil(($ms + 1000) / 1000);
|
||||
if ($this->isSupportFloatMinutes) {
|
||||
$minutes = sprintf('-%.2f', $seconds / 60);
|
||||
} else {
|
||||
$minutes = sprintf('-%d', ceil($seconds / 60));
|
||||
}
|
||||
Timer::tick($ms, function () use ($channel, $minutes) {
|
||||
global $fileModifyTimes;
|
||||
if (is_null($fileModifyTimes)) {
|
||||
$fileModifyTimes = [];
|
||||
}
|
||||
|
||||
$seconds = ceil(($ms + 1000) / 1000);
|
||||
$minutes = sprintf('-%.2f', $seconds / 60);
|
||||
|
||||
[$fileModifyTimes, $changedFiles] = $this->scan($fileModifyTimes, $minutes);
|
||||
|
||||
foreach ($changedFiles as $file) {
|
||||
@ -70,27 +90,27 @@ class FindDriver implements DriverInterface
|
||||
{
|
||||
$changedFiles = [];
|
||||
$dest = implode(' ', $targets);
|
||||
$ret = System::exec($this->getBin() . ' ' . $dest . ' -mmin ' . $minutes . ' -type f -printf "%p %T+' . PHP_EOL . '"');
|
||||
$ret = System::exec($this->getBin() . ' ' . $dest . ' -mmin ' . $minutes . ' -type f -print');
|
||||
if ($ret['code'] === 0 && strlen($ret['output'])) {
|
||||
$stdout = $ret['output'];
|
||||
|
||||
$lineArr = explode(PHP_EOL, $stdout);
|
||||
foreach ($lineArr as $line) {
|
||||
$fileArr = explode(' ', $line);
|
||||
if (count($fileArr) == 2) {
|
||||
$pathName = $fileArr[0];
|
||||
$modifyTime = $fileArr[1];
|
||||
|
||||
if (! empty($ext) && ! Str::endsWith($pathName, $ext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($fileModifyTimes[$pathName]) && $fileModifyTimes[$pathName] == $modifyTime) {
|
||||
continue;
|
||||
}
|
||||
$fileModifyTimes[$pathName] = $modifyTime;
|
||||
$changedFiles[] = $pathName;
|
||||
$pathName = $line;
|
||||
$modifyTime = fileatime($pathName);
|
||||
// modifyTime less than or equal to startTime continue
|
||||
if ($modifyTime <= $this->startTime) {
|
||||
continue;
|
||||
}
|
||||
if (! empty($ext) && ! Str::endsWith($pathName, $ext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($fileModifyTimes[$pathName]) && $fileModifyTimes[$pathName] == $modifyTime) {
|
||||
continue;
|
||||
}
|
||||
$fileModifyTimes[$pathName] = $modifyTime;
|
||||
$changedFiles[] = $pathName;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user