diff options
153 files changed, 5530 insertions, 10138 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f99bbf18..d0c9a309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,63 +1,533 @@ # CHANGELOG -# v2.7.0 (?.?.2022) +## v2.7.0 (14.01.2022) ## 👀 New: - ✏️ RR `workers pool`, `worker`, `worker_watcher` now has their own log levels. `stderr/stdout` logged as before at the `info` log level. All other messages moved to the `debug` log level except a few events from the `worker_watcher` when RR can't allocate the new worker which are moved to the `warn`. - ✏️ Use the common logger for the whole roadrunner-sdk and roadrunner-plugins. -- ✏️ Add `codec` field to the `protocol` structure **[internal change]**. +- ✏️ `.rr.yaml` now support versions. You may safely use your old configurations w/o specifying versions. Configuration w/o version will be treated as `2.6`. It is safe to use configuration w/o version or with version `2.6` with RR `2.7` because RR is able to automatically transform the old configuration. + But if you use configuration version `2.7` you must update the `jobs` pipelines config. + **At this point we can guarantee, that no breaking changes will be introduced in the configuration w/o auto-convert from the older configuration version** + For example, if we introduce a configuration update let's say in version `2.10`, we will support automatic conversion from at least 2 previous versions w/o involving the user into the process. In the example case, versions `2.9` and `2.8` will be automatically converted. From our release cycle, you will have at least 3 months to update the configuration from version `2.8` and 2 months from `2.9`.Version located at the top of the `.rr.yaml`: + +**Compatibility matrix located here**: TODO +**Configuration changelog**: TODO + +```yaml +version: "2.6" + +# ..... PLUGINS ...... +``` + +**Before:** +```yaml + pipelines: + test-local: + driver: memory + priority: 10 + prefetch: 10000 + + test-local-1: + driver: boltdb + priority: 10 + file: "rr.db" + prefetch: 10000 + + test-local-2: + driver: amqp + prefetch: 10 + priority: 1 + queue: test-1-queue + exchange: default + exchange_type: direct + routing_key: test + exclusive: false + multiple_ack: false + requeue_on_fail: false + + test-local-3: + driver: beanstalk + priority: 11 + tube_priority: 1 + tube: default-1 + reserve_timeout: 10s + + test-local-4: + driver: sqs + priority: 10 + prefetch: 10 + visibility_timeout: 0 + wait_time_seconds: 0 + queue: default + attributes: + DelaySeconds: 0 + MaximumMessageSize: 262144 + MessageRetentionPeriod: 345600 + ReceiveMessageWaitTimeSeconds: 0 + VisibilityTimeout: 30 + tags: + test: "tag" + + test-local-5: + driver: nats + priority: 2 + prefetch: 100 + subject: default + stream: foo + deliver_new: true + rate_limit: 100 + delete_stream_on_stop: false + delete_after_ack: false +``` + +**After**: +Now, pipelines have only `driver` key with the configuration under the `config` key. We did that to uniform configuration across all drivers (like in the `KV`). +```yaml + pipelines: + test-local: + driver: memory + + config: # <------------------ NEW + priority: 10 + prefetch: 10000 + + test-local-1: + driver: boltdb + + config: # <------------------ NEW + priority: 10 + file: "test-local-1-bolt.db" + prefetch: 10000 + + test-local-2: + driver: amqp + + config: # <------------------ NEW + priority: 11 + prefetch: 100 + queue: test-12-queue + exchange: default + exchange_type: direct + routing_key: test + exclusive: false + multiple_ack: false + requeue_on_fail: false + + test-local-3: + driver: beanstalk + + config: # <------------------ NEW + priority: 11 + tube_priority: 1 + tube: default-2 + reserve_timeout: 10s + + test-local-4: + driver: sqs + + config: # <------------------ NEW + priority: 10 + prefetch: 10 + visibility_timeout: 0 + wait_time_seconds: 0 + queue: default + + attributes: + DelaySeconds: 0 + MaximumMessageSize: 262144 + MessageRetentionPeriod: 345600 + ReceiveMessageWaitTimeSeconds: 0 + VisibilityTimeout: 30 + tags: + test: "tag" + + test-local-5: + driver: nats + + config: # <------------------ NEW + priority: 2 + prefetch: 100 + subject: default + stream: foo + deliver_new: true + rate_limit: 100 + delete_stream_on_stop: false + delete_after_ack: false +``` + +- ✏️ **[ALPHA]** New cache http middleware. It is still in alpha, but we started implementing the [rfc-7234](https://httpwg.org/specs/rfc7234.html) to support `Cache-Control` and caching in general. In the first alpha you may test the `max-age`, `Age` and `Authorization` support via the in-memory driver. + +**Configuration**: +```yaml +http: +# ..... + middleware: ["cache"] + cache: + driver: memory + cache_methods: ["GET", "HEAD", "POST"] # only GET in alpha + config: {} # empty configuration for the memory +``` + +- ✏️ Logger unification. Starting this version we bound our logs to the `uber/zap` log library as one of the most popular and extensible. +- ✏️ API stabilization. All `v2` api interfaces moved to the `https://github.com/roadrunner-server/api` repository. Except logger (structure), all plugins depends only on the interfaces and don't import each other. +- ✏️ `GRPC` plugin now is able to work with gzipped payloads. [FR](https://github.com/spiral/roadrunner-plugins/issues/191) (reporter @hetao29) +- ✏️ `SQS` plugin now detects EC2 env and uses AWS credentials instead of static provider. [FR](https://github.com/spiral/roadrunner-plugins/issues/142) (reporter @paulermo) +- ✏️ `Jobs` plugin now acknowledges responses with incorrectly formed responses to prevent the infinity loop (with the error message in the logs). [BUG](https://github.com/spiral/roadrunner-plugins/issues/190) (reporter @sergey-telpuk) +- ✏️ `protoc` updated to the version `v3.19.2`. ## 🩹 Fixes: - 🐛 Fix: RR may have missed the message from the `stderr` when the PHP script failed to start immediately after RR starts. +- 🐛 Fix: 200 HTTP status code instead of 400 on readiness/health bad requests. [BUG](https://github.com/spiral/roadrunner-plugins/issues/180) +- 🐛 Fix: `new_relic` plugin removes/modifies more headers than it should. [BUG](https://github.com/spiral/roadrunner-plugins/issues/185) (reporter: @arku31) ---- -# v2.6.3 (23.12.2021) +## v2.6.6 (7.12.2021) ## 👀 New: -- ✏️ Add `Reset` method to the static pool interface. Should be used to reset the pool instead of destroying and replacing it. +- ✏️ Add events from the supervisor to the `server` plugin. +- +## 🩹 Fixes: +- 🐛 Fix: worker exited immediately after obtaining the response. [BUG](https://github.com/spiral/roadrunner/issues/871) (reporter: @samdark). + +## 📦 Packages: + +- 📦 Update RoadRunner to `v2.6.2` + +## v2.6.5 (7.12.2021) + +## 🩹 Fixes: +- 🐛 Fix: wrong metrics type for the `rr_http_requests_queue`, [bug](https://github.com/spiral/roadrunner-plugins/issues/162) (reporter: @victor-sudakov) +- 🐛 Fix: memory leak when supervised static pool used. [PR](https://github.com/spiral/roadrunner/pull/870). + +## 📦 Packages: + +- 📦 Update RoadRunner to `v2.6.1` --- -# v2.6.2 (15.12.2021) +## v2.6.4 (7.12.2021) + +## 📦 Packages: + +- 📦 Update endure to `v1.1.0` ## 🩹 Fixes: +- 🐛 Fix: NPE in the `http.Reset`. [BUG](https://github.com/spiral/roadrunner-plugins/issues/155) -- 🐛 Fix: worker exited immediately after obtaining the response. [BUG](https://github.com/spiral/roadrunner/issues/871) (reporter: @samdark). +--- + +## v2.6.3 (3.12.2021) + +## 👀 New: +- ✏️ `informer.List` RPC call return all available plugins with workers instead of all available plugins. This behavior was changed because `Informer` has the dependency of every RR plugin, which led to the cycles. This is not an external API and used only internally. +- ✏️ Beanstalk queue returned to the **[ALPHA]** stage. It's very unstable when destroying pipelines and can lead to infinite read loops when something wrong with the connection. Use with care. +- ✏️ Go version updated to `v1.17.4`. + +## 🩹 Fixes: +- 🐛 Fix: add missing plugins to the container: `fileserver`, `http_metrics`. --- -# v2.6.1 (14.12.2021) +## v2.6.2 (3.12.2021) ## 🩹 Fixes: -- 🐛 Fix: memory leak when supervised static pool used. [PR](https://github.com/spiral/roadrunner/pull/870). +- 🐛 Fix: Random NPE on RR start. [BUG](https://github.com/spiral/roadrunner-plugins/issues/143) --- -# v2.6.0 (30.11.2021) +## v2.6.1 (2.12.2021) + +## 🩹 Fixes: + +- 🐛 Fix: logger incorrectly escaped HTML, JSON, and other special symbols. + +--- -### 👀 New: +## v2.6.0 (30.11.2021) +## 👀 New: - ✏️ New internal message bus. Available globally. Supports wildcard subscriptions (for example: `http.*` will subscribe you to the all events coming from the `http` plugin). The subscriptions can be made from any RR plugin to any RR plugin. - ✏️ Now, RR will show in the returned error the bad header content in case of CRC mismatch error. More info in the [PR](https://github.com/spiral/roadrunner/pull/863). +- ✏️ **[BETA]** Support for the New Relic observability platform. Sample of the client library might be + found [here](https://github.com/arku31/roadrunner-newrelic). (Thanks @arku31) + New Relic middleware is a part of the HTTP plugin, thus configuration should be inside it: + +```yaml +http: + address: 127.0.0.1:15389 + middleware: [ "new_relic" ] <------- NEW + new_relic: <---------- NEW + app_name: "app" + license_key: "key" + pool: + num_workers: 10 + allocate_timeout: 60s + destroy_timeout: 60s +``` + +License key and application name could be set via environment variables: (leave `app_name` and `license_key` empty) + +- license_key: `NEW_RELIC_LICENSE_KEY`. +- app_name: `NEW_RELIC_APP_NAME`. + +To set the New Relic attributes, the PHP worker should send headers values withing the `rr_newrelic` header key. +Attributes should be separated by the `:`, for example `foo:bar`, where `foo` is a key and `bar` is a value. New Relic +attributes sent from the worker will not appear in the HTTP response, they will be sent directly to the New Relic. + +To see the sample of the PHP library, see the @arku31 implementation: https://github.com/arku31/roadrunner-newrelic + +The special key which PHP may set to overwrite the transaction name is: `transaction_name`. For +example: `transaction_name:foo` means: set transaction name as `foo`. By default, `RequestURI` is used as the +transaction name. + +```php + $resp = new \Nyholm\Psr7\Response(); + $rrNewRelic = [ + 'shopId:1', //custom data + 'auth:password', //custom data + 'transaction_name:test_transaction' //name - special key to override the name. By default it will use requestUri. + ]; + + $resp = $resp->withHeader('rr_newrelic', $rrNewRelic); +``` + +--- + +- ✏️ **[BETA]** New plugin: `TCP`. The TCP plugin is used to handle raw TCP payload with a bi-directional [protocol](tcp/docs/tcp.md) between the RR server and PHP worker. + +PHP client library: https://github.com/spiral/roadrunner-tcp + +Configuration: +```yaml +rpc: + listen: tcp://127.0.0.1:6001 + +server: + command: "php ../../psr-worker-tcp-cont.php" + +tcp: + servers: + server1: + addr: 127.0.0.1:7778 + delimiter: "\r\n" + server2: + addr: 127.0.0.1:8811 + read_buf_size: 10 + server3: + addr: 127.0.0.1:8812 + delimiter: "\r\n" + read_buf_size: 1 + + pool: + num_workers: 5 + max_jobs: 0 + allocate_timeout: 60s + destroy_timeout: 60s +``` + +--- + +- ✏️ New HTTP middleware: `http_metrics`. +```yaml +http: + address: 127.0.0.1:15389 + middleware: [ "http_metrics" ] <------- NEW + pool: + num_workers: 10 + allocate_timeout: 60s + destroy_timeout: 60s +``` +All old and new http metrics will be available after the middleware is activated. Be careful, this middleware may slow down your requests. New metrics: + + - `rr_http_requests_queue_sum` - number of queued requests. + - `rr_http_no_free_workers_total` - number of the occurrences of the `NoFreeWorkers` errors. + + +----- + +- ✏️ New file server to serve static files. It works on a different address, so it doesn't affect the HTTP performance. It uses advanced configuration specific for the static file servers. It can handle any number of directories with its own HTTP prefixes. + Config: + +```yaml +fileserver: + # File server address + # + # Error on empty + address: 127.0.0.1:10101 + # Etag calculation. Request body CRC32. + # + # Default: false + calculate_etag: true + + # Weak etag calculation + # + # Default: false + weak: false + + # Enable body streaming for the files more than 4KB + # + # Default: false + stream_request_body: true + + serve: + # HTTP prefix + # + # Error on empty + - prefix: "/foo" + + # Directory to serve + # + # Default: "." + root: "../../../tests" + + # When set to true, the server tries minimizing CPU usage by caching compressed files + # + # Default: false + compress: false + + # Expiration duration for inactive file handlers. Units: seconds. + # + # Default: 10, use a negative value to disable it. + cache_duration: 10 + + # The value for the Cache-Control HTTP-header. Units: seconds + # + # Default: 10 seconds + max_age: 10 + + # Enable range requests + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests + # + # Default: false + bytes_range: true + + - prefix: "/foo/bar" + root: "../../../tests" + compress: false + cache_duration: 10s + max_age: 10 + bytes_range: true +``` + +- ✏️ `on_init` option for the `server` plugin. `on_init` code executed before the regular command and can be used to warm up the application for example. Failed `on_init` command doesn't affect the main command, so, the RR will continue to run. Thanks (@OO00O0O) + +Config: +```yaml +# Application server settings (docs: https://roadrunner.dev/docs/php-worker) +server: + on_init: <----------- NEW + # Command to execute before the main server's command + # + # This option is required if using on_init + command: "any php or script here" + + # Script execute timeout + # + # Default: 60s [60m, 60h], if used w/o units its means - NANOSECONDS. + exec_timeout: 20s + + # Environment variables for the worker processes. + # + # Default: <empty map> + env: + - SOME_KEY: "SOME_VALUE" + - SOME_KEY2: "SOME_VALUE2" + + # ..REGULAR SERVER OPTIONS... +``` + +--- + +- ✏️ **[BETA]** GRPC can handle multiply proto files. + Config: +```yaml +# GRPC service configuration +grpc: + # Proto files to use + # + # This option is required. At least one proto file must be specified. + proto: + - "first.proto" + - "second.proto" + +## ... OTHER REGULAR GRPC OPTIONS ... +``` + +--- + +- ✏️ New `allow` configuration option for the `http.uploads` and multipart requests. The new option allows you to filter upload extensions knowing only allowed. Now, there is no need to have a looong list with all possible extensions to forbid. [FR](https://github.com/spiral/roadrunner-plugins/issues/123) (Thanks @rjd22) + `http.uploads.forbid` has a higher priority, so, if you have duplicates in the `http.uploads.allow` and `http.uploads.forbid` the duplicated extension will be forbidden. + Config: + +```yaml +http: + address: 127.0.0.1:18903 + max_request_size: 1024 + middleware: ["pluginMiddleware", "pluginMiddleware2"] + uploads: + forbid: [".php", ".exe", ".bat"] + allow: [".html", ".aaa" ] <------------- NEW + trusted_subnets: + [ + "10.0.0.0/8", + "127.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "::1/128", + "fc00::/7", + "fe80::/10", + ] + pool: + num_workers: 2 + max_jobs: 0 + allocate_timeout: 60s + destroy_timeout: 60s +``` + +- ✏️ Beanstalk queue reject stop RPC calls if there are jobs in the priority queue associated with the requested + pipeline. + +- ✏️ Startup message when the RR has started. ## 🩹 Fixes: +- 🐛 Fix: GRPC server will show message when started. +- 🐛 Fix: Static plugin headers were added to all requests. [BUG](https://github.com/spiral/roadrunner-plugins/issues/115) - 🐛 Fix: zombie processes in the `pool.debug` mode. ---- +## 📦 Packages: + +- 📦 roadrunner `v2.6.0` +- 📦 roadrunner-plugins `v2.6.0` +- 📦 roadrunner-temporal `v1.0.11` +- 📦 endure `v1.0.8` +- 📦 goridge `v3.2.4` +- 📦 temporal.io/sdk `v1.11.1` + +## v2.5.3 (27.10.2021) + +## 🩹 Fixes: + +- 🐛 Fix: panic in the TLS layer. The `http` plugin used `http` server instead of `https` in the rootCA routine. + +## v2.5.2 (23.10.2021) + +## 🩹 Fixes: -# v2.5.1 (07.11.2021) +- 🐛 Fix: ASLR builds causes [problems](https://github.com/spiral/roadrunner-binary/issues/120) in the docker. + + +## v2.5.1 (22.10.2021) ## 🩹 Fixes: -- 🐛 Fix: worker's channel deadlock when using [TTL](https://github.com/spiral/roadrunner/issues/853). Thanks @dstrop +- 🐛 Fix: [base64](https://github.com/spiral/roadrunner-plugins/issues/86) response instead of json in some edge cases. -# v2.5.0 (20.10.2021) +## v2.5.0 (20.10.2021) # 💔 Breaking change: @@ -69,9 +539,9 @@ ```yaml broadcast: - default: - driver: memory - interval: 1 + default: + driver: memory + interval: 1 ``` ### New style: @@ -80,7 +550,7 @@ broadcast: broadcast: default: driver: memory - config: { } <--------------- NEW + config: {} <--------------- NEW ``` ```yaml @@ -94,8 +564,8 @@ kv: memcached-rr: driver: memcached config: <--------------- NEW - addr: - - "127.0.0.1:11211" + addr: + - "127.0.0.1:11211" broadcast: default: @@ -108,11 +578,8 @@ broadcast: ## 👀 New: - ✏️ **[BETA]** GRPC plugin update to v2. -- ✏️ [Roadrunner-plugins](https://github.com/spiral/roadrunner-plugins) repository. This is the new home for the - roadrunner plugins with documentation, configuration samples, and common problems. -- ✏️ **[BETA]** Let's Encrypt support. RR now can obtain an SSL certificate/PK for your domain automatically. Here is - the new configuration: - +- ✏️ [Roadrunner-plugins](https://github.com/spiral/roadrunner-plugins) repository. This is the new home for the roadrunner plugins with documentation, configuration samples, and common problems. +- ✏️ **[BETA]** Let's Encrypt support. RR now can obtain an SSL certificate/PK for your domain automatically. Here is the new configuration: ```yaml ssl: # Host and port to listen on (eg.: `127.0.0.1:443`). @@ -165,25 +632,23 @@ broadcast: - ✏️ Add a new option to the `logs` plugin to configure the line ending. By default, used `\n`. **New option**: - ```yaml # Logs plugin settings logs: - (....) - # Line ending - # - # Default: "\n". - line_ending: "\n" + (....) + # Line ending + # + # Default: "\n". + line_ending: "\n" ``` - ✏️ HTTP [Access log support](https://github.com/spiral/roadrunner-plugins/issues/34) at the `Info` log level. - ```yaml http: address: 127.0.0.1:55555 max_request_size: 1024 access_logs: true <-------- Access Logs ON/OFF - middleware: [ ] + middleware: [] pool: num_workers: 2 @@ -191,16 +656,13 @@ http: allocate_timeout: 60s destroy_timeout: 60s ``` - -- ✏️ HTTP middleware to handle `X-Sendfile` [header](https://github.com/spiral/roadrunner-plugins/issues/9). Middleware - reads the file in 10MB chunks. So, for example for the 5Gb file, only 10MB of RSS will be used. If the file size is - smaller than 10MB, the middleware fits the buffer to the file size. - +- ✏️ HTTP middleware to handle `X-Sendfile` [header](https://github.com/spiral/roadrunner-plugins/issues/9). + Middleware reads the file in 10MB chunks. So, for example for the 5Gb file, only 10MB of RSS will be used. If the file size is smaller than 10MB, the middleware fits the buffer to the file size. ```yaml http: address: 127.0.0.1:44444 max_request_size: 1024 - middleware: [ "sendfile" ] <----- NEW MIDDLEWARE + middleware: ["sendfile"] <----- NEW MIDDLEWARE pool: num_workers: 2 @@ -210,7 +672,6 @@ http: ``` - ✏️ Service plugin now supports env variables passing to the script/executable/binary/any like in the `server` plugin: - ```yaml service: some_service_1: @@ -218,25 +679,21 @@ service: process_num: 1 exec_timeout: 5s # s,m,h (seconds, minutes, hours) remain_after_exit: true - env: <----------------- NEW + env: <----------------- NEW foo: "BAR" restart_sec: 1 ``` - ✏️ Server plugin can accept scripts (sh, bash, etc) in it's `command` configuration key: - ```yaml server: - command: "./script.sh OR sh script.sh" <--- UPDATED - relay: "pipes" - relay_timeout: "20s" + command: "./script.sh OR sh script.sh" <--- UPDATED + relay: "pipes" + relay_timeout: "20s" ``` - -The script should start a worker as the last command. For the `pipes`, scripts should not contain programs, which can -close `stdin`, `stdout` or `stderr`. +The script should start a worker as the last command. For the `pipes`, scripts should not contain programs, which can close `stdin`, `stdout` or `stderr`. - ✏️ Nats jobs driver support - [PR](https://github.com/spiral/roadrunner-plugins/pull/68). - ```yaml nats: addr: "demo.nats.io" @@ -264,14 +721,12 @@ jobs: consume: [ "test-1" ] ``` - - Driver uses NATS JetStream API and is not compatible with non-js API. -- ✏️ Response API for the NATS, RabbitMQ, SQS and Beanstalk drivers. This means, that you'll be able to respond to a - specified in the response queue. Limitations: - - To send a response to the queue maintained by the RR, you should send it as a `Job` type. There are no limitations - for the responses into the other queues (tubes, subjects). +- ✏️ Response API for the NATS, RabbitMQ, SQS and Beanstalk drivers. This means, that you'll be able to respond to a specified in the response queue. + Limitations: + - To send a response to the queue maintained by the RR, you should send it as a `Job` type. There are no limitations for the responses into the other queues (tubes, subjects). - Driver uses the same endpoint (address) to send the response as specified in the configuration. ## 🩹 Fixes: @@ -294,46 +749,38 @@ jobs: ## 🩹 Fixes: -- 🐛 Fix: bug with not-idempotent call to the `attributes.Init`. -- 🐛 Fix: memory jobs driver behavior. Now memory driver starts consuming automatically if the user consumes the - pipeline in the configuration. +- 🐛 Fix: bug with not-idempotent call to the `attributes.Init`. +- 🐛 Fix: memory jobs driver behavior. Now memory driver starts consuming automatically if the user consumes the pipeline in the configuration. ## v2.4.0 (02.09.2021) ## 💔 Internal BC: -- 🔨 Pool, worker interfaces: payload now passed and returned by the pointer. +- 🔨 Pool, worker interfaces: payload now passed and returned by the pointer. ## 👀 New: -- ✏️ Long-awaited, reworked `Jobs` plugin with pluggable drivers. Now you can allocate/destroy pipelines in the runtime. - Drivers included in the initial release: `RabbitMQ (0-9-1)`, `SQS v2`, `beanstalk`, `memory` and local queue powered - by the `boltdb`. [PR](https://github.com/spiral/roadrunner/pull/726) -- ✏️ Support for the IPv6 (`tcp|http(s)|empty [::]:port`, `tcp|http(s)|empty [::1]:port` - , `tcp|http(s)|empty :// [0:0:0:0:0:0:0:1]:port`) for RPC, HTTP and other - plugins. [RFC](https://datatracker.ietf.org/doc/html/rfc2732#section-2) -- ✏️ Support for the Docker images via GitHub packages. -- ✏️ Go 1.17 support for the all spiral packages. +- ✏️ Long-awaited, reworked `Jobs` plugin with pluggable drivers. Now you can allocate/destroy pipelines in the runtime. Drivers included in the initial release: `RabbitMQ (0-9-1)`, `SQS v2`, `beanstalk`, `memory` and local queue powered by the `boltdb`. [PR](https://github.com/spiral/roadrunner/pull/726) +- ✏️ Support for the IPv6 (`tcp|http(s)|empty [::]:port`, `tcp|http(s)|empty [::1]:port`, `tcp|http(s)|empty :// [0:0:0:0:0:0:0:1]:port`) for RPC, HTTP and other plugins. [RFC](https://datatracker.ietf.org/doc/html/rfc2732#section-2) +- ✏️ Support for the Docker images via GitHub packages. +- ✏️ Go 1.17 support for the all spiral packages. ## 🩹 Fixes: -- 🐛 Fix: fixed bug with goroutines waiting on the internal worker's container - channel, [issue](https://github.com/spiral/roadrunner/issues/750). -- 🐛 Fix: RR become unresponsive when new workers failed to - re-allocate, [issue](https://github.com/spiral/roadrunner/issues/772). -- 🐛 Fix: add `debug` pool config key to the `.rr.yaml` - configuration [reference](https://github.com/spiral/roadrunner-binary/issues/79). +- 🐛 Fix: fixed bug with goroutines waiting on the internal worker's container channel, [issue](https://github.com/spiral/roadrunner/issues/750). +- 🐛 Fix: RR become unresponsive when new workers failed to re-allocate, [issue](https://github.com/spiral/roadrunner/issues/772). +- 🐛 Fix: add `debug` pool config key to the `.rr.yaml` configuration [reference](https://github.com/spiral/roadrunner-binary/issues/79). ## 📦 Packages: -- 📦 Update goridge to `v3.2.1` -- 📦 Update temporal to `v1.0.9` -- 📦 Update endure to `v1.0.4` +- 📦 Update goridge to `v3.2.1` +- 📦 Update temporal to `v1.0.9` +- 📦 Update endure to `v1.0.4` ## 📈 Summary: -- RR Milestone [2.4.0](https://github.com/spiral/roadrunner/milestone/29?closed=1) -- RR-Binary Milestone [2.4.0](https://github.com/spiral/roadrunner-binary/milestone/10?closed=1) +- RR Milestone [2.4.0](https://github.com/spiral/roadrunner/milestone/29?closed=1) +- RR-Binary Milestone [2.4.0](https://github.com/spiral/roadrunner-binary/milestone/10?closed=1) --- @@ -341,13 +788,13 @@ jobs: ## 🩹 Fixes: -- 🐛 Fix: Do not call the container's Stop method after the container stopped by an error. -- 🐛 Fix: Bug with ttl incorrectly handled by the worker [PR](https://github.com/spiral/roadrunner/pull/749) -- 🐛 Fix: Add `RR_BROADCAST_PATH` to the `websockets` plugin [PR](https://github.com/spiral/roadrunner/pull/749) +- 🐛 Fix: Do not call the container's Stop method after the container stopped by an error. +- 🐛 Fix: Bug with ttl incorrectly handled by the worker [PR](https://github.com/spiral/roadrunner/pull/749) +- 🐛 Fix: Add `RR_BROADCAST_PATH` to the `websockets` plugin [PR](https://github.com/spiral/roadrunner/pull/749) ## 📈 Summary: -- RR Milestone [2.3.2](https://github.com/spiral/roadrunner/milestone/31?closed=1) +- RR Milestone [2.3.2](https://github.com/spiral/roadrunner/milestone/31?closed=1) --- @@ -355,32 +802,32 @@ jobs: ## 👀 New: -- ✏️ Rework `broadcast` plugin. Add architecture diagrams to the `doc` - folder. [PR](https://github.com/spiral/roadrunner/pull/732) -- ✏️ Add `Clear` method to the KV plugin RPC. [PR](https://github.com/spiral/roadrunner/pull/736) +- ✏️ Rework `broadcast` plugin. Add architecture diagrams to the `doc` + folder. [PR](https://github.com/spiral/roadrunner/pull/732) +- ✏️ Add `Clear` method to the KV plugin RPC. [PR](https://github.com/spiral/roadrunner/pull/736) ## 🩹 Fixes: -- 🐛 Fix: Bug with channel deadlock when `exec_ttl` was used and TTL limit - reached [PR](https://github.com/spiral/roadrunner/pull/738) -- 🐛 Fix: Bug with healthcheck endpoint when workers were marked as invalid and stay is that state until next - request [PR](https://github.com/spiral/roadrunner/pull/738) -- 🐛 Fix: Bugs with `boltdb` storage: [Boom](https://github.com/spiral/roadrunner/issues/717) - , [Boom](https://github.com/spiral/roadrunner/issues/718), [Boom](https://github.com/spiral/roadrunner/issues/719) -- 🐛 Fix: Bug with incorrect redis initialization and usage [Bug](https://github.com/spiral/roadrunner/issues/720) -- 🐛 Fix: Bug, Goridge duplicate error messages [Bug](https://github.com/spiral/goridge/issues/128) -- 🐛 Fix: Bug, incorrect request `origin` check [Bug](https://github.com/spiral/roadrunner/issues/727) +- 🐛 Fix: Bug with channel deadlock when `exec_ttl` was used and TTL limit + reached [PR](https://github.com/spiral/roadrunner/pull/738) +- 🐛 Fix: Bug with healthcheck endpoint when workers were marked as invalid and stay is that state until next + request [PR](https://github.com/spiral/roadrunner/pull/738) +- 🐛 Fix: Bugs with `boltdb` storage: [Boom](https://github.com/spiral/roadrunner/issues/717) + , [Boom](https://github.com/spiral/roadrunner/issues/718), [Boom](https://github.com/spiral/roadrunner/issues/719) +- 🐛 Fix: Bug with incorrect redis initialization and usage [Bug](https://github.com/spiral/roadrunner/issues/720) +- 🐛 Fix: Bug, Goridge duplicate error messages [Bug](https://github.com/spiral/goridge/issues/128) +- 🐛 Fix: Bug, incorrect request `origin` check [Bug](https://github.com/spiral/roadrunner/issues/727) ## 📦 Packages: -- 📦 Update goridge to `v3.1.4` -- 📦 Update temporal to `v1.0.8` +- 📦 Update goridge to `v3.1.4` +- 📦 Update temporal to `v1.0.8` ## 📈 Summary: -- RR Milestone [2.3.1](https://github.com/spiral/roadrunner/milestone/30?closed=1) -- Temporal Milestone [1.0.8](https://github.com/temporalio/roadrunner-temporal/milestone/11?closed=1) -- Goridge Milestone [3.1.4](https://github.com/spiral/goridge/milestone/11?closed=1) +- RR Milestone [2.3.1](https://github.com/spiral/roadrunner/milestone/30?closed=1) +- Temporal Milestone [1.0.8](https://github.com/temporalio/roadrunner-temporal/milestone/11?closed=1) +- Goridge Milestone [3.1.4](https://github.com/spiral/goridge/milestone/11?closed=1) --- @@ -388,36 +835,36 @@ jobs: ## 👀 New: -- ✏️ Brand new `broadcast` plugin now has the name - `websockets` with broadcast capabilities. It can handle hundreds of - thousands websocket connections very efficiently (~300k messages per second with 1k connected clients, in-memory bus - on 2CPU cores and 1GB of RAM) [Issue](https://github.com/spiral/roadrunner/issues/513) -- ✏️ Protobuf binary messages for the `websockets` and `kv` RPC calls under the - hood. [Issue](https://github.com/spiral/roadrunner/issues/711) -- ✏️ Json-schemas for the config file v1.0 (it also registered - in [schemastore.org](https://github.com/SchemaStore/schemastore/pull/1614)) -- ✏️ `latest` docker image tag supported now (but we strongly recommend using a versioned tag (like `0.2.3`) instead) -- ✏️ Add new option to the `http` config section: `internal_error_code` to override default (500) internal error - code. [Issue](https://github.com/spiral/roadrunner/issues/659) -- ✏️ Expose HTTP plugin metrics (workers memory, requests count, requests duration) - . [Issue](https://github.com/spiral/roadrunner/issues/489) -- ✏️ Scan `server.command` and find errors related to the wrong path to a `PHP` file, or `.ph`, `.sh` - scripts. [Issue](https://github.com/spiral/roadrunner/issues/658) -- ✏️ Support file logger with log rotation [Wiki](https://en.wikipedia.org/wiki/Log_rotation) - , [Issue](https://github.com/spiral/roadrunner/issues/545) +- ✏️ Brand new `broadcast` plugin now has the name - `websockets` with broadcast capabilities. It can handle hundreds of + thousands websocket connections very efficiently (~300k messages per second with 1k connected clients, in-memory bus + on 2CPU cores and 1GB of RAM) [Issue](https://github.com/spiral/roadrunner/issues/513) +- ✏️ Protobuf binary messages for the `websockets` and `kv` RPC calls under the + hood. [Issue](https://github.com/spiral/roadrunner/issues/711) +- ✏️ Json-schemas for the config file v1.0 (it also registered + in [schemastore.org](https://github.com/SchemaStore/schemastore/pull/1614)) +- ✏️ `latest` docker image tag supported now (but we strongly recommend using a versioned tag (like `0.2.3`) instead) +- ✏️ Add new option to the `http` config section: `internal_error_code` to override default (500) internal error + code. [Issue](https://github.com/spiral/roadrunner/issues/659) +- ✏️ Expose HTTP plugin metrics (workers memory, requests count, requests duration) + . [Issue](https://github.com/spiral/roadrunner/issues/489) +- ✏️ Scan `server.command` and find errors related to the wrong path to a `PHP` file, or `.ph`, `.sh` + scripts. [Issue](https://github.com/spiral/roadrunner/issues/658) +- ✏️ Support file logger with log rotation [Wiki](https://en.wikipedia.org/wiki/Log_rotation) + , [Issue](https://github.com/spiral/roadrunner/issues/545) ## 🩹 Fixes: -- 🐛 Fix: Bug with `informer.Workers` worked incorrectly: [Bug](https://github.com/spiral/roadrunner/issues/686) -- 🐛 Fix: Internal error messages will not be shown to the user (except HTTP status code). Error message will be in - logs: [Bug](https://github.com/spiral/roadrunner/issues/659) -- 🐛 Fix: Error message will be properly shown in the log in case of `SoftJob` - error: [Bug](https://github.com/spiral/roadrunner/issues/691) -- 🐛 Fix: Wrong applied middlewares for the `fcgi` server leads to the - NPE: [Bug](https://github.com/spiral/roadrunner/issues/701) +- 🐛 Fix: Bug with `informer.Workers` worked incorrectly: [Bug](https://github.com/spiral/roadrunner/issues/686) +- 🐛 Fix: Internal error messages will not be shown to the user (except HTTP status code). Error message will be in + logs: [Bug](https://github.com/spiral/roadrunner/issues/659) +- 🐛 Fix: Error message will be properly shown in the log in case of `SoftJob` + error: [Bug](https://github.com/spiral/roadrunner/issues/691) +- 🐛 Fix: Wrong applied middlewares for the `fcgi` server leads to the + NPE: [Bug](https://github.com/spiral/roadrunner/issues/701) ## 📦 Packages: -- 📦 Update goridge to `v3.1.0` +- 📦 Update goridge to `v3.1.0` --- @@ -425,9 +872,9 @@ jobs: ## 🩹 Fixes: -- 🐛 Fix: revert static plugin. It stays as a separate plugin on the main route (`/`) and supports all the previously - announced features. -- 🐛 Fix: remove `build` and other old targets from the Makefile. +- 🐛 Fix: revert static plugin. It stays as a separate plugin on the main route (`/`) and supports all the previously + announced features. +- 🐛 Fix: remove `build` and other old targets from the Makefile. --- @@ -435,21 +882,21 @@ jobs: ## 👀 New: -- ✏️ Reworked `static` plugin. Now, it does not affect the performance of the main route and persist on the separate - file server (within the `http` plugin). Looong awaited feature: `Etag` (+ weak Etags) as well with the `If-Mach` - , `If-None-Match`, `If-Range`, `Last-Modified` - and `If-Modified-Since` tags supported. Static plugin has a bunch of new options such as: `allow`, `calculate_etag` - , `weak` and `pattern`. +- ✏️ Reworked `static` plugin. Now, it does not affect the performance of the main route and persist on the separate + file server (within the `http` plugin). Looong awaited feature: `Etag` (+ weak Etags) as well with the `If-Mach` + , `If-None-Match`, `If-Range`, `Last-Modified` + and `If-Modified-Since` tags supported. Static plugin has a bunch of new options such as: `allow`, `calculate_etag` + , `weak` and `pattern`. - ### Option `always` was deleted from the plugin. + ### Option `always` was deleted from the plugin. -- ✏️ Update `informer.List` implementation. Now it returns a list with the all available plugins in the runtime. +- ✏️ Update `informer.List` implementation. Now it returns a list with the all available plugins in the runtime. ## 🩹 Fixes: -- 🐛 Fix: issue with wrong ordered middlewares (reverse). Now the order is correct. -- 🐛 Fix: issue when RR fails if a user sets `debug` mode with the `exec_ttl` supervisor option. -- 🐛 Fix: uniform log levels. Use everywhere the same levels (warn, error, debug, info, panic). +- 🐛 Fix: issue with wrong ordered middlewares (reverse). Now the order is correct. +- 🐛 Fix: issue when RR fails if a user sets `debug` mode with the `exec_ttl` supervisor option. +- 🐛 Fix: uniform log levels. Use everywhere the same levels (warn, error, debug, info, panic). --- @@ -457,102 +904,102 @@ jobs: ## 🩹 Fixes: -- 🐛 Fix: issue with endure provided wrong logger interface implementation. +- 🐛 Fix: issue with endure provided wrong logger interface implementation. ## v2.1.0 (27.04.2021) ## 👀 New: -- ✏️ New `service` plugin. Docs: [link](https://roadrunner.dev/docs/beep-beep-service) -- ✏️ Stabilize `kv` plugin with `boltdb`, `in-memory`, `memcached` and `redis` drivers. +- ✏️ New `service` plugin. Docs: [link](https://roadrunner.dev/docs/beep-beep-service) +- ✏️ Stabilize `kv` plugin with `boltdb`, `in-memory`, `memcached` and `redis` drivers. ## 🩹 Fixes: -- 🐛 Fix: Logger didn't provide an anonymous log instance to a plugins w/o `Named` interface implemented. -- 🐛 Fix: http handler was without log listener after `rr reset`. +- 🐛 Fix: Logger didn't provide an anonymous log instance to a plugins w/o `Named` interface implemented. +- 🐛 Fix: http handler was without log listener after `rr reset`. ## v2.0.4 (06.04.2021) ## 👀 New: -- ✏️ Add support for `linux/arm64` platform for docker image (thanks @tarampampam). -- ✏️ Add dotenv file support (`.env` in working directory by default; file location can be changed using CLI - flag `--dotenv` or `DOTENV_PATH` environment variable) (thanks @tarampampam). -- 📜 Add a new `raw` mode for the `logger` plugin to keep the stderr log message of the worker unmodified (logger - severity level should be at least `INFO`). -- 🆕 Add Readiness probe check. The `status` plugin provides `/ready` endpoint which return the `204` HTTP code if there - are no workers in the `Ready` state and `200 OK` status if there are at least 1 worker in the `Ready` state. +- ✏️ Add support for `linux/arm64` platform for docker image (thanks @tarampampam). +- ✏️ Add dotenv file support (`.env` in working directory by default; file location can be changed using CLI + flag `--dotenv` or `DOTENV_PATH` environment variable) (thanks @tarampampam). +- 📜 Add a new `raw` mode for the `logger` plugin to keep the stderr log message of the worker unmodified (logger + severity level should be at least `INFO`). +- 🆕 Add Readiness probe check. The `status` plugin provides `/ready` endpoint which return the `204` HTTP code if there + are no workers in the `Ready` state and `200 OK` status if there are at least 1 worker in the `Ready` state. ## 🩹 Fixes: -- 🐛 Fix: bug with the temporal worker which does not follow general graceful shutdown period. +- 🐛 Fix: bug with the temporal worker which does not follow general graceful shutdown period. ## v2.0.3 (29.03.2021) ## 🩹 Fixes: -- 🐛 Fix: slow last response when reached `max_jobs` limit. +- 🐛 Fix: slow last response when reached `max_jobs` limit. ## v2.0.2 (06.04.2021) -- 🐛 Fix: Bug with required Root CA certificate for the SSL, now it's optional. -- 🐛 Fix: Bug with incorrectly consuming metrics collector from the RPC calls (thanks @dstrop). -- 🆕 New: HTTP/FCGI/HTTPS internal logs instead of going to the raw stdout will be displayed in the RR logger at - the `Info` log level. -- ⚡ New: Builds for the Mac with the M1 processor (arm64). -- 👷 Rework ServeHTTP handler logic. Use http.Error instead of writing code directly to the response writer. Other small - improvements. +- 🐛 Fix: Bug with required Root CA certificate for the SSL, now it's optional. +- 🐛 Fix: Bug with incorrectly consuming metrics collector from the RPC calls (thanks @dstrop). +- 🆕 New: HTTP/FCGI/HTTPS internal logs instead of going to the raw stdout will be displayed in the RR logger at + the `Info` log level. +- ⚡ New: Builds for the Mac with the M1 processor (arm64). +- 👷 Rework ServeHTTP handler logic. Use http.Error instead of writing code directly to the response writer. Other small + improvements. ## v2.0.1 (09.03.2021) -- 🐛 Fix: incorrect PHP command validation -- 🐛 Fix: ldflags properly inject RR version -- ⬆️ Update: README, links to the go.pkg from v1 to v2 -- 📦 Bump golang version in the Dockerfile and in the `go.mod` to 1.16 -- 📦 Bump Endure container to v1.0.0. +- 🐛 Fix: incorrect PHP command validation +- 🐛 Fix: ldflags properly inject RR version +- ⬆️ Update: README, links to the go.pkg from v1 to v2 +- 📦 Bump golang version in the Dockerfile and in the `go.mod` to 1.16 +- 📦 Bump Endure container to v1.0.0. ## v2.0.0 (02.03.2021) -- ✔️ Add a shared server to create PHP worker pools instead of isolated worker pool in each individual plugin. -- 🆕 New plugin system with auto-recovery, easier plugin API. -- 📜 New `logger` plugin to configure logging for each plugin individually. -- 🔝 Up to 50% performance increase in HTTP workloads. -- ✔️ Add **[Temporal Workflow](https://temporal.io)** plugin to run distributed computations on scale. -- ✔️ Add `debug` flag to reload PHP worker ahead of a request (emulates PHP-FPM behavior). -- ❌ Eliminate `limit` service, now each worker pool includes `supervisor` configuration. -- 🆕 New resetter, informer plugins to perform hot reloads and observe loggers in a system. -- 💫 Expose more HTTP plugin configuration options. -- 🆕 Headers, static and gzip services now located in HTTP config. -- 🆕 Ability to configure the middleware sequence. -- 💣 Faster Goridge protocol (eliminated 50% of syscalls). -- 💾 Add support for binary payloads for RPC (`msgpack`). -- 🆕 Server no longer stops when a PHP worker dies (attempts to restart). -- 💾 New RR binary server downloader. -- 💣 Echoing no longer breaks execution (yay!). -- 🆕 Migration to ZapLogger instead of Logrus. -- 💥 RR can no longer stuck when studding down with broken tasks in a pipeline. -- 🧪 More tests, more static analysis. -- 💥 Create a new foundation for new KV, WebSocket, GRPC and Queue plugins. +- ✔️ Add a shared server to create PHP worker pools instead of isolated worker pool in each individual plugin. +- 🆕 New plugin system with auto-recovery, easier plugin API. +- 📜 New `logger` plugin to configure logging for each plugin individually. +- 🔝 Up to 50% performance increase in HTTP workloads. +- ✔️ Add **[Temporal Workflow](https://temporal.io)** plugin to run distributed computations on scale. +- ✔️ Add `debug` flag to reload PHP worker ahead of a request (emulates PHP-FPM behavior). +- ❌ Eliminate `limit` service, now each worker pool includes `supervisor` configuration. +- 🆕 New resetter, informer plugins to perform hot reloads and observe loggers in a system. +- 💫 Expose more HTTP plugin configuration options. +- 🆕 Headers, static and gzip services now located in HTTP config. +- 🆕 Ability to configure the middleware sequence. +- 💣 Faster Goridge protocol (eliminated 50% of syscalls). +- 💾 Add support for binary payloads for RPC (`msgpack`). +- 🆕 Server no longer stops when a PHP worker dies (attempts to restart). +- 💾 New RR binary server downloader. +- 💣 Echoing no longer breaks execution (yay!). +- 🆕 Migration to ZapLogger instead of Logrus. +- 💥 RR can no longer stuck when studding down with broken tasks in a pipeline. +- 🧪 More tests, more static analysis. +- 💥 Create a new foundation for new KV, WebSocket, GRPC and Queue plugins. ## v2.0.0-RC.4 (20.02.2021) -- PHP tests use latest signatures (https://github.com/spiral/roadrunner/pull/550). -- Endure container update to v1.0.0-RC.2 version. -- Remove unneeded mutex from the `http.Workers` method. -- Rename `checker` plugin package to `status`, remove `/v1` endpoint prefix (#557). -- Add static, headers, status, gzip plugins to the `main.go`. -- Fix workers pool behavior -> idle_ttl, ttl, max_memory are soft errors and exec_ttl is hard error. +- PHP tests use latest signatures (https://github.com/spiral/roadrunner/pull/550). +- Endure container update to v1.0.0-RC.2 version. +- Remove unneeded mutex from the `http.Workers` method. +- Rename `checker` plugin package to `status`, remove `/v1` endpoint prefix (#557). +- Add static, headers, status, gzip plugins to the `main.go`. +- Fix workers pool behavior -> idle_ttl, ttl, max_memory are soft errors and exec_ttl is hard error. ## v2.0.0-RC.3 (17.02.2021) -- Add support for the overwriting `.rr.yaml` keys with values (ref: https://roadrunner.dev/docs/intro-config) -- Make logger plugin optional to define in the config. Default values: level -> `debug`, mode -> `development` -- Add the ability to read env variables from the `.rr.yaml` in the form of: `rpc.listen: {RPC_ADDR}`. Reference: - ref: https://roadrunner.dev/docs/intro-config (Environment Variables paragraph) +- Add support for the overwriting `.rr.yaml` keys with values (ref: https://roadrunner.dev/docs/intro-config) +- Make logger plugin optional to define in the config. Default values: level -> `debug`, mode -> `development` +- Add the ability to read env variables from the `.rr.yaml` in the form of: `rpc.listen: {RPC_ADDR}`. Reference: + ref: https://roadrunner.dev/docs/intro-config (Environment Variables paragraph) ## v2.0.0-RC.2 (11.02.2021) -- Update RR to version v2.0.0-RC.2 -- Update Temporal plugin to version v2.0.0-RC.1 -- Update Goridge to version v3.0.1 -- Update Endure to version v1.0.0-RC.1 +- Update RR to version v2.0.0-RC.2 +- Update Temporal plugin to version v2.0.0-RC.1 +- Update Goridge to version v3.0.1 +- Update Endure to version v1.0.0-RC.1 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 49aeb3c8..ae0b283a 100755 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -43,4 +43,4 @@ Project maintainers who do not follow or enforce the Code of Conduct in good fai This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org -[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct/ +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ac4981bf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# Image page: <https://hub.docker.com/_/golang> +FROM --platform=${TARGETPLATFORM:-linux/amd64} golang:alpine as builder + +# app version and build date must be passed during image building (version without any prefix). +# e.g.: `docker build --build-arg "APP_VERSION=1.2.3" --build-arg "BUILD_TIME=$(date +%FT%T%z)" .` +ARG APP_VERSION="undefined" +ARG BUILD_TIME="undefined" + +COPY . /src + +WORKDIR /src + +# arguments to pass on each go tool link invocation +ENV LDFLAGS="-s \ +-X github.com/spiral/roadrunner-binary/v2/internal/meta.version=$APP_VERSION \ +-X github.com/spiral/roadrunner-binary/v2/internal/meta.buildTime=$BUILD_TIME" + +# compile binary file +RUN set -x \ + && CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./rr ./cmd/rr \ + && ./rr -v + +# Image page: <https://hub.docker.com/_/alpine> +# https://alpinelinux.org/posts/Alpine-3.13.4-released.html +# Critical issue with 3.13.3 https://nvd.nist.gov/vuln/detail/CVE-2021-28831 +FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3 + +# use same build arguments for image labels +ARG APP_VERSION="undefined" +ARG BUILD_TIME="undefined" + +LABEL \ + org.opencontainers.image.title="roadrunner" \ + org.opencontainers.image.description="High-performance PHP application server, load-balancer and process manager" \ + org.opencontainers.image.url="https://github.com/spiral/roadrunner-binary" \ + org.opencontainers.image.source="https://github.com/spiral/roadrunner-binary" \ + org.opencontainers.image.vendor="SpiralScout" \ + org.opencontainers.image.version="$APP_VERSION" \ + org.opencontainers.image.created="$BUILD_TIME" \ + org.opencontainers.image.licenses="MIT" + +# copy required files from builder image +COPY --from=builder /src/rr /usr/bin/rr +COPY --from=builder /src/.rr.yaml /etc/rr.yaml + +# use roadrunner binary as image entrypoint +ENTRYPOINT ["/usr/bin/rr"] @@ -1,29 +1,4 @@ -#!/usr/bin/make -# Makefile readme (ru): <http://linux.yaroslavl.ru/docs/prog/gnu_make_3-79_russian_manual.html> -# Makefile readme (en): <https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents> - -SHELL = /bin/sh - -test_coverage: - rm -rf coverage-ci - mkdir ./coverage-ci - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage-ci/pipe.out -covermode=atomic ./ipc/pipe - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage-ci/socket.out -covermode=atomic ./ipc/socket - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage-ci/pool.out -covermode=atomic ./pool - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage-ci/worker.out -covermode=atomic ./worker - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage-ci/bst.out -covermode=atomic ./bst - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage-ci/pq.out -covermode=atomic ./priority_queue - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage-ci/worker_stack.out -covermode=atomic ./worker_watcher - go test -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./coverage-ci/events.out -covermode=atomic ./events - echo 'mode: atomic' > ./coverage-ci/summary.txt - tail -q -n +2 ./coverage-ci/*.out >> ./coverage-ci/summary.txt - -test: ## Run application tests - go test -v -race -tags=debug ./ipc/pipe - go test -v -race -tags=debug ./ipc/socket - go test -v -race -tags=debug ./pool - go test -v -race -tags=debug ./worker - go test -v -race -tags=debug ./worker_watcher - go test -v -race -tags=debug ./bst - go test -v -race -tags=debug ./priority_queue - go test -v -race -tags=debug ./events +test: + go test -v -race ./... +build: + CGO_ENABLED=0 go build -trimpath -ldflags "-s" -o rr qcmd/rr/main.go diff --git a/Makefile.docker b/Makefile.docker new file mode 100644 index 00000000..db70f9bf --- /dev/null +++ b/Makefile.docker @@ -0,0 +1,37 @@ +#!/usr/bin/make +# Makefile manual (ru): <http://linux.yaroslavl.ru/docs/prog/gnu_make_3-79_russian_manual.html> +# Makefile manual (en): <https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents> + +SHELL = /bin/sh +GO_VERSION = 1.17 +DOCKER_GO_RUN_ARGS = --rm -v "$(shell pwd):/src" -w "/src" -u "$(shell id -u):$(shell id -g)" -e "GOPATH=/tmp" -e "HOME=/tmp" + +.PHONY : help build fmt lint gotest test +.DEFAULT_GOAL : help + +# This will output the help for each task. Thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +help: ## Show this help + @printf "\033[33m%s:\033[0m\n" 'Available commands' + @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[32m%-11s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + +image: ## Build docker image with app + docker build -f ./Dockerfile -t rr:local . + @printf "\n \e[30;42m %s \033[0m\n\n" 'Now you can use image like `docker run --rm rr:local ...`'; + +build: ## Build app binary file + docker run $(DOCKER_GO_RUN_ARGS) -e "CGO_ENABLED=0" "golang:$(GO_VERSION)-buster" \ + go build -trimpath -ldflags "-s" -o ./rr ./cmd/rr + +fmt: ## Run source code formatter tools + docker run $(DOCKER_GO_RUN_ARGS) -e "GO111MODULE=off" "golang:$(GO_VERSION)-buster" \ + sh -c 'go get golang.org/x/tools/cmd/goimports && $$GOPATH/bin/goimports -d -w .' + docker run $(DOCKER_GO_RUN_ARGS) "golang:$(GO_VERSION)-buster" \ + sh -c 'gofmt -s -w -d .; go mod tidy' + +lint: ## Run app linters + docker run $(DOCKER_GO_RUN_ARGS) -t golangci/golangci-lint:v1.39-alpine golangci-lint run + +gotest: ## Run app tests + docker run $(DOCKER_GO_RUN_ARGS) -t "golang:$(GO_VERSION)-buster" go test -v -race -timeout 10s ./... + +test: lint gotest ## Run app tests and linters @@ -2,16 +2,13 @@ <img src="https://user-images.githubusercontent.com/796136/50286124-6f7f3780-046f-11e9-9f45-e8fedd4f786d.png" height="75px" alt="RoadRunner"> </p> <p align="center"> - <a href="https://packagist.org/packages/spiral/roadrunner"><img src="https://poser.pugx.org/spiral/roadrunner/version"></a> - <a href="https://pkg.go.dev/github.com/spiral/roadrunner/v2?tab=doc"><img src="https://godoc.org/github.com/spiral/roadrunner/v2?status.svg"></a> - <a href="https://github.com/spiral/roadrunner/actions"><img src="https://github.com/spiral/roadrunner/workflows/Linux/badge.svg" alt=""></a> - <a href="https://github.com/spiral/roadrunner/actions"><img src="https://github.com/spiral/roadrunner/workflows/Linters/badge.svg" alt=""></a> - <a href="https://goreportcard.com/report/github.com/spiral/roadrunner"><img src="https://goreportcard.com/badge/github.com/spiral/roadrunner"></a> - <a href="https://scrutinizer-ci.com/g/spiral/roadrunner/?branch=master"><img src="https://scrutinizer-ci.com/g/spiral/roadrunner/badges/quality-score.png"></a> - <a href="https://codecov.io/gh/spiral/roadrunner/"><img src="https://codecov.io/gh/spiral/roadrunner/branch/master/graph/badge.svg"></a> - <a href="https://lgtm.com/projects/g/spiral/roadrunner/alerts/"><img alt="Total alerts" src="https://img.shields.io/lgtm/alerts/g/spiral/roadrunner.svg?logo=lgtm&logoWidth=18"/></a> + <a href="https://github.com/spiral/roadrunner-binary/releases"><img src="https://img.shields.io/github/v/release/spiral/roadrunner-binary.svg?maxAge=30"></a> + <a href="https://pkg.go.dev/github.com/spiral/roadrunner-binary/v2"><img src="https://godoc.org/github.com/spiral/roadrunner-binary/v2?status.svg"></a> + <a href="https://github.com/spiral/roadrunner-binary/actions"><img src="https://github.com/spiral/roadrunner-binary/workflows/tests/badge.svg"></a> + <a href="https://goreportcard.com/report/github.com/spiral/roadrunner-binary"><img src="https://goreportcard.com/badge/github.com/spiral/roadrunner-binary"></a> + <a href="https://lgtm.com/projects/g/spiral/roadrunner-binary/alerts/"><img alt="Total alerts" src="https://img.shields.io/lgtm/alerts/g/spiral/roadrunner-binary.svg?logo=lgtm&logoWidth=18"/></a> <a href="https://discord.gg/TFeEmCs"><img src="https://img.shields.io/badge/discord-chat-magenta.svg"></a> - <a href="https://packagist.org/packages/spiral/roadrunner"><img src="https://img.shields.io/packagist/dd/spiral/roadrunner?style=flat-square"></a> + <img alt="All releases" src="https://img.shields.io/github/downloads/spiral/roadrunner-binary/total"> </p> RoadRunner is an open-source (MIT licensed) high-performance PHP application server, load balancer, and process manager. @@ -23,8 +20,7 @@ with much greater performance and flexibility. <p align="center"> <a href="https://roadrunner.dev/"><b>Official Website</b></a> | <a href="https://roadrunner.dev/docs"><b>Documentation</b></a> | - <a href="https://github.com/orgs/spiral/projects/2"><b>Release schedule</b></a> | - <a href="https://github.com/spiral/roadrunner-plugins"><b>Plugins</b></a> + <a href="https://github.com/orgs/spiral/projects/2"><b>Release schedule</b></a> </p> Features: @@ -66,10 +62,12 @@ $ ./vendor/bin/rr get-binary ``` > For getting roadrunner binary file you can use our docker image: `spiralscout/roadrunner:X.X.X` (more information about -> image and tags can be found [here](https://hub.docker.com/r/spiralscout/roadrunner/)) +> image and tags can be found [here](https://hub.docker.com/r/spiralscout/roadrunner/)). +> +> Important notice: we strongly recommend to use a versioned tag (like `1.2.3`) instead `latest`. Configuration can be located in `.rr.yaml` -file ([full sample](https://github.com/spiral/roadrunner-binary/blob/master/.rr.yaml)): +file ([full sample](https://github.com/spiral/roadrunner/blob/master/.rr.yaml)): ```yaml rpc: @@ -127,11 +125,3 @@ License: -------- The MIT License (MIT). Please see [`LICENSE`](./LICENSE) for more information. Maintained by [Spiral Scout](https://spiralscout.com). - -## Contributors - -Thanks to all the people who already contributed! - -<a href="https://github.com/spiral/roadrunner/graphs/contributors"> - <img src="https://contributors-img.web.app/image?repo=spiral/roadrunner" /> -</a> diff --git a/bors.toml b/bors.toml deleted file mode 100755 index a2d7cb25..00000000 --- a/bors.toml +++ /dev/null @@ -1,9 +0,0 @@ -status = [ - 'Linux / Build (Go 1.17.4, PHP 7.4, OS ubuntu-latest)', - 'Linux / Build (Go 1.17.4, PHP 8.0, OS ubuntu-latest)', - 'Linux / Build (Go 1.17.4, PHP 8.1, OS ubuntu-latest)', - 'Linters / Golang-CI (lint) ', -] -required_approvals = 0 -delete_merged_branches = true -timeout-sec = 1800 diff --git a/bst/bst.go b/bst/bst.go deleted file mode 100644 index dab9346c..00000000 --- a/bst/bst.go +++ /dev/null @@ -1,152 +0,0 @@ -package bst - -// BST ... -type BST struct { - // registered topic, not unique - topic string - // associated connections with the topic - uuids map[string]struct{} - - // left and right subtrees - left *BST - right *BST -} - -func NewBST() Storage { - return &BST{ - uuids: make(map[string]struct{}, 10), - } -} - -// Insert uuid to the topic -func (b *BST) Insert(uuid string, topic string) { - curr := b - - for { - if topic == curr.topic { - curr.uuids[uuid] = struct{}{} - return - } - // if topic less than curr topic - if topic < curr.topic { - if curr.left == nil { - curr.left = &BST{ - topic: topic, - uuids: map[string]struct{}{uuid: {}}, - } - return - } - // move forward - curr = curr.left - } else { - if curr.right == nil { - curr.right = &BST{ - topic: topic, - uuids: map[string]struct{}{uuid: {}}, - } - return - } - - curr = curr.right - } - } -} - -func (b *BST) Contains(topic string) bool { - curr := b - for curr != nil { - switch { - case topic < curr.topic: - curr = curr.left - case topic > curr.topic: - curr = curr.right - case topic == curr.topic: - return true - } - } - - return false -} - -func (b *BST) Get(topic string) map[string]struct{} { - curr := b - for curr != nil { - switch { - case topic < curr.topic: - curr = curr.left - case topic > curr.topic: - curr = curr.right - case topic == curr.topic: - return curr.uuids - } - } - - return nil -} - -func (b *BST) Remove(uuid string, topic string) { - b.removeHelper(uuid, topic, nil) -} - -func (b *BST) removeHelper(uuid string, topic string, parent *BST) { - curr := b - for curr != nil { - if topic < curr.topic { //nolint:gocritic - parent = curr - curr = curr.left - } else if topic > curr.topic { - parent = curr - curr = curr.right - } else { - // if more than 1 topic - remove only topic, do not remove the whole vertex - if len(curr.uuids) > 1 { - if _, ok := curr.uuids[uuid]; ok { - delete(curr.uuids, uuid) - return - } - } - - if curr.left != nil && curr.right != nil { //nolint:gocritic - curr.topic, curr.uuids = curr.right.traverseForMinString() - curr.right.removeHelper(curr.topic, uuid, curr) - } else if parent == nil { - if curr.left != nil { //nolint:gocritic - curr.topic = curr.left.topic - curr.uuids = curr.left.uuids - - curr.right = curr.left.right - curr.left = curr.left.left - } else if curr.right != nil { - curr.topic = curr.right.topic - curr.uuids = curr.right.uuids - - curr.left = curr.right.left - curr.right = curr.right.right - } else { //nolint:staticcheck - // single node tree - } - } else if parent.left == curr { - if curr.left != nil { - parent.left = curr.left - } else { - parent.left = curr.right - } - } else if parent.right == curr { - if curr.left != nil { - parent.right = curr.left - } else { - parent.right = curr.right - } - } - break - } - } -} - -//go:inline -func (b *BST) traverseForMinString() (string, map[string]struct{}) { - if b.left == nil { - return b.topic, b.uuids - } - return b.left.traverseForMinString() -} diff --git a/bst/bst_test.go b/bst/bst_test.go deleted file mode 100644 index 2afbee10..00000000 --- a/bst/bst_test.go +++ /dev/null @@ -1,368 +0,0 @@ -package bst - -import ( - "math/rand" - "testing" - "time" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -const predifined = "chat-1-2" - -func TestNewBST(t *testing.T) { - // create a new bst - g := NewBST() - - for i := 0; i < 100; i++ { - g.Insert(uuid.NewString(), "comments") - } - - for i := 0; i < 100; i++ { - g.Insert(uuid.NewString(), "comments2") - } - - for i := 0; i < 100; i++ { - g.Insert(uuid.NewString(), "comments3") - } - - // should be 100 - exist := g.Get("comments") - assert.Len(t, exist, 100) - - // should be 100 - exist2 := g.Get("comments2") - assert.Len(t, exist2, 100) - - // should be 100 - exist3 := g.Get("comments3") - assert.Len(t, exist3, 100) -} - -func TestBSTContains(t *testing.T) { - // create a new bst - g := NewBST() - - for i := 0; i < 100; i++ { - g.Insert(uuid.NewString(), "comments") - } - - for i := 0; i < 100; i++ { - g.Insert(uuid.NewString(), "comments2") - } - - for i := 0; i < 100; i++ { - g.Insert(uuid.NewString(), "comments3") - } - - exist := g.Contains("comments") - assert.True(t, exist) - - exist2 := g.Contains("comments2") - assert.True(t, exist2) - - exist3 := g.Contains("comments3") - assert.True(t, exist3) - - exist4 := g.Contains("comments5") - assert.False(t, exist4) - - exist5 := g.Contains("comments6") - assert.False(t, exist5) -} - -func TestBSTRemove(t *testing.T) { - // create a new bst - g := NewBST() - u := uuid.NewString() - g.Insert(u, "comments") - g.Remove(u, "comments") - - res := g.Contains("comments") - assert.False(t, res) -} - -func BenchmarkGraph(b *testing.B) { - g := NewBST() - - for i := 0; i < 1000; i++ { - uid := uuid.New().String() - g.Insert(uuid.NewString(), uid) - } - - g.Insert(uuid.NewString(), predifined) - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - exist := g.Get(predifined) - _ = exist - } -} - -func BenchmarkBigSearch(b *testing.B) { - g1 := NewBST() - g2 := NewBST() - g3 := NewBST() - - predefinedSlice := make([]string, 0, 1000) - for i := 0; i < 1000; i++ { - predefinedSlice = append(predefinedSlice, uuid.NewString()) - } - if predefinedSlice == nil { - b.FailNow() - } - - for i := 0; i < 1000; i++ { - g1.Insert(uuid.NewString(), uuid.NewString()) - } - for i := 0; i < 1000; i++ { - g2.Insert(uuid.NewString(), uuid.NewString()) - } - for i := 0; i < 1000; i++ { - g3.Insert(uuid.NewString(), uuid.NewString()) - } - - for i := 0; i < 333; i++ { - g1.Insert(uuid.NewString(), predefinedSlice[i]) - } - - for i := 0; i < 333; i++ { - g2.Insert(uuid.NewString(), predefinedSlice[333+i]) - } - - for i := 0; i < 333; i++ { - g3.Insert(uuid.NewString(), predefinedSlice[666+i]) - } - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - for i := 0; i < 333; i++ { - exist := g1.Get(predefinedSlice[i]) - _ = exist - } - } - for i := 0; i < b.N; i++ { - for i := 0; i < 333; i++ { - exist := g2.Get(predefinedSlice[333+i]) - _ = exist - } - } - for i := 0; i < b.N; i++ { - for i := 0; i < 333; i++ { - exist := g3.Get(predefinedSlice[666+i]) - _ = exist - } - } -} - -func BenchmarkBigSearchWithRemoves(b *testing.B) { - g1 := NewBST() - g2 := NewBST() - g3 := NewBST() - - predefinedSlice := make([]string, 0, 1000) - for i := 0; i < 1000; i++ { - predefinedSlice = append(predefinedSlice, uuid.NewString()) - } - if predefinedSlice == nil { - b.FailNow() - } - - for i := 0; i < 1000; i++ { - g1.Insert(uuid.NewString(), uuid.NewString()) - } - for i := 0; i < 1000; i++ { - g2.Insert(uuid.NewString(), uuid.NewString()) - } - for i := 0; i < 1000; i++ { - g3.Insert(uuid.NewString(), uuid.NewString()) - } - - for i := 0; i < 333; i++ { - g1.Insert(uuid.NewString(), predefinedSlice[i]) - } - - for i := 0; i < 333; i++ { - g2.Insert(uuid.NewString(), predefinedSlice[333+i]) - } - - for i := 0; i < 333; i++ { - g3.Insert(uuid.NewString(), predefinedSlice[666+i]) - } - - go func() { - tt := time.NewTicker(time.Millisecond) - for { - select { - case <-tt.C: - num := rand.Intn(333) //nolint:gosec - values := g1.Get(predefinedSlice[num]) - for k := range values { - g1.Remove(k, predefinedSlice[num]) - } - } - } - }() - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - for i := 0; i < 333; i++ { - exist := g1.Get(predefinedSlice[i]) - _ = exist - } - } - for i := 0; i < b.N; i++ { - for i := 0; i < 333; i++ { - exist := g2.Get(predefinedSlice[333+i]) - _ = exist - } - } - for i := 0; i < b.N; i++ { - for i := 0; i < 333; i++ { - exist := g3.Get(predefinedSlice[666+i]) - _ = exist - } - } -} - -func TestGraph(t *testing.T) { - g := NewBST() - - for i := 0; i < 1000; i++ { - uid := uuid.New().String() - g.Insert(uuid.NewString(), uid) - } - - g.Insert(uuid.NewString(), predifined) - - exist := g.Get(predifined) - assert.NotNil(t, exist) - assert.Len(t, exist, 1) -} - -func TestTreeConcurrentContains(t *testing.T) { - g := NewBST() - - key1 := uuid.NewString() - key2 := uuid.NewString() - key3 := uuid.NewString() - key4 := uuid.NewString() - key5 := uuid.NewString() - - g.Insert(key1, predifined) - g.Insert(key2, predifined) - g.Insert(key3, predifined) - g.Insert(key4, predifined) - g.Insert(key5, predifined) - - for i := 0; i < 100; i++ { - go func() { - _ = g.Get(predifined) - }() - - go func() { - _ = g.Get(predifined) - }() - - go func() { - _ = g.Get(predifined) - }() - - go func() { - _ = g.Get(predifined) - }() - } - - time.Sleep(time.Second * 2) - - exist := g.Get(predifined) - assert.NotNil(t, exist) - assert.Len(t, exist, 5) -} - -func TestGraphRemove(t *testing.T) { - g := NewBST() - - key1 := uuid.NewString() - key2 := uuid.NewString() - key3 := uuid.NewString() - key4 := uuid.NewString() - key5 := uuid.NewString() - - g.Insert(key1, predifined) - g.Insert(key2, predifined) - g.Insert(key3, predifined) - g.Insert(key4, predifined) - g.Insert(key5, predifined) - - exist := g.Get(predifined) - assert.NotNil(t, exist) - assert.Len(t, exist, 5) - - g.Remove(key1, predifined) - - exist = g.Get(predifined) - assert.NotNil(t, exist) - assert.Len(t, exist, 4) -} - -func TestBigSearch(t *testing.T) { - g1 := NewBST() - g2 := NewBST() - g3 := NewBST() - - predefinedSlice := make([]string, 0, 1000) - for i := 0; i < 1000; i++ { - predefinedSlice = append(predefinedSlice, uuid.NewString()) - } - if predefinedSlice == nil { - t.FailNow() - } - - for i := 0; i < 1000; i++ { - g1.Insert(uuid.NewString(), uuid.NewString()) - } - for i := 0; i < 1000; i++ { - g2.Insert(uuid.NewString(), uuid.NewString()) - } - for i := 0; i < 1000; i++ { - g3.Insert(uuid.NewString(), uuid.NewString()) - } - - for i := 0; i < 333; i++ { - g1.Insert(uuid.NewString(), predefinedSlice[i]) - } - - for i := 0; i < 333; i++ { - g2.Insert(uuid.NewString(), predefinedSlice[333+i]) - } - - for i := 0; i < 333; i++ { - g3.Insert(uuid.NewString(), predefinedSlice[666+i]) - } - - for i := 0; i < 333; i++ { - exist := g1.Get(predefinedSlice[i]) - assert.NotNil(t, exist) - assert.Len(t, exist, 1) - } - - for i := 0; i < 333; i++ { - exist := g2.Get(predefinedSlice[333+i]) - assert.NotNil(t, exist) - assert.Len(t, exist, 1) - } - - for i := 0; i < 333; i++ { - exist := g3.Get(predefinedSlice[666+i]) - assert.NotNil(t, exist) - assert.Len(t, exist, 1) - } -} diff --git a/bst/doc.go b/bst/doc.go deleted file mode 100644 index abb7e6e9..00000000 --- a/bst/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -package bst - -/* -Binary search tree for the pubsub - -The vertex may have one or multiply topics associated with the single websocket connection UUID -*/ diff --git a/bst/interface.go b/bst/interface.go deleted file mode 100644 index 95b03e11..00000000 --- a/bst/interface.go +++ /dev/null @@ -1,13 +0,0 @@ -package bst - -// Storage is general in-memory BST storage implementation -type Storage interface { - // Insert inserts to a vertex with topic ident connection uuid - Insert(uuid string, topic string) - // Remove removes uuid from topic, if the uuid is single for a topic, whole vertex will be removed - Remove(uuid, topic string) - // Get will return all connections associated with the topic - Get(topic string) map[string]struct{} - // Contains checks if the BST contains a topic - Contains(topic string) bool -} diff --git a/codecov.yml b/codecov.yml index 7554dabf..672717e3 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,38 +3,10 @@ coverage: project: default: target: auto - threshold: 50% + threshold: 0% informational: true patch: default: target: auto - threshold: 50% + threshold: 0% informational: true - -# do not include tests folders -ignore: - - "common" - - "internal/protocol.go" - - "proto" - - "utils" - - "tests" - - "systemd" - - "doc" - # is not ready yet - - "worker_watcher/container/queue" - - "bst/bst_test.go" - - "bst/doc.go" - - "bst/interface.go" - - "pool/static_pool_test.go" - - "pool/supervisor_test.go" - - "transport/pipe/pipe_factory_spawn_test.go" - - "transport/pipe/pipe_factory_test.go" - - "transport/socket/socket_factory_spawn_test.go" - - "transport/socket/socket_factory_test.go" - - "transport/interface.go" - - "worker/state_test.go" - - "worker/sync_worker_test.go" - - "worker/worker_test.go" - - "events/pool_events.go" - - "events/worker_events.go" - - "events/jobs_events.go" diff --git a/composer.json b/composer.json deleted file mode 100644 index 878d29ae..00000000 --- a/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "spiral/roadrunner", - "type": "metapackage", - "description": "RoadRunner: High-performance PHP application server, load-balancer and process manager written in Golang", - "license": "MIT", - "authors": [ - { - "name": "Anton Titov / Wolfy-J", - "email": "[email protected]" - }, - { - "name": "RoadRunner Community", - "homepage": "https://github.com/spiral/roadrunner/graphs/contributors" - } - ], - "require": { - "spiral/roadrunner-worker": "^2.0", - "spiral/roadrunner-cli": "^2.0", - "spiral/roadrunner-http": "^2.0" - }, - "config": { - "sort-packages": true - }, - "minimum-stability": "dev", - "prefer-stable": true -} diff --git a/dev/.rr-docker.yaml b/dev/.rr-docker.yaml new file mode 100644 index 00000000..c84179bf --- /dev/null +++ b/dev/.rr-docker.yaml @@ -0,0 +1,33 @@ +rpc: + listen: tcp://0.0.0.0:6001 + +server: + command: "php /etc/psr-worker.php" + + +http: + address: 0.0.0.0:15395 + max_request_size: 1024 + middleware: [ "websockets" ] + uploads: + forbid: [ ".php", ".exe", ".bat" ] + trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] + pool: + num_workers: 1 + +redis: + addrs: + - "redis:6379" + +websockets: + pubsubs: [ "memory" ] + path: "/ws" + +logs: + mode: production + level: error + +endure: + grace_period: 10s + print_graph: false + log_level: panic diff --git a/dev/Dockerfile.local b/dev/Dockerfile.local new file mode 100644 index 00000000..f990b3ee --- /dev/null +++ b/dev/Dockerfile.local @@ -0,0 +1,39 @@ +# Image page: <https://hub.docker.com/_/golang> +FROM --platform=${TARGETPLATFORM:-linux/amd64} golang:alpine as builder + +# app version and build date must be passed during image building (version without any prefix). +# e.g.: `docker build --build-arg "APP_VERSION=1.2.3" --build-arg "BUILD_TIME=$(date +%FT%T%z)" .` +ARG APP_VERSION="undefined" +ARG BUILD_TIME="undefined" + +COPY . /src + +# Image page: <https://hub.docker.com/_/alpine> +# https://alpinelinux.org/posts/Alpine-3.13.4-released.html +# Critical issue with 3.13.3 https://nvd.nist.gov/vuln/detail/CVE-2021-28831 +FROM --platform=${TARGETPLATFORM:-linux/amd64} php:latest + +# use same build arguments for image labels +ARG APP_VERSION="undefined" +ARG BUILD_TIME="undefined" + +# copy required files from builder image +COPY --from=builder /src/rr /usr/bin/rr +COPY --from=builder /src/.rr-docker.yaml /etc/rr-docker.yaml +COPY --from=builder /src/composer.json /etc/composer.json +COPY --from=builder /src/psr-worker.php /etc/psr-worker.php + +WORKDIR /etc + +RUN docker-php-ext-install sockets + +RUN apt update -y +RUN apt upgrade -y +RUN apt install git zip unzip -y + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +RUN composer install + +# use roadrunner binary as image entrypoint +ENTRYPOINT ["/usr/bin/rr"] diff --git a/dev/Makefile b/dev/Makefile new file mode 100644 index 00000000..71c0ed5b --- /dev/null +++ b/dev/Makefile @@ -0,0 +1,2 @@ +build: + CGO_ENABLED=0 go build -trimpath -ldflags "-s" -o rr ../cmd/rr/main.go diff --git a/dev/README.md b/dev/README.md new file mode 100644 index 00000000..0d055321 --- /dev/null +++ b/dev/README.md @@ -0,0 +1,39 @@ +# Local testing/development files + +### Usage: +1. Build the RR binary with the following command: `make build`. As the result you'll see a RR binary. +2. If you are testing you own plugin (Go), you may use `replace` directive in the root `go.mod` file, for example: +```go +module github.com/spiral/roadrunner-binary/v2 + +go 1.17 + +require ( + github.com/buger/goterm v1.0.1 + github.com/dustin/go-humanize v1.0.0 + github.com/fatih/color v1.12.0 + github.com/joho/godotenv v1.3.0 + github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d + github.com/mattn/go-runewidth v0.0.13 + github.com/olekukonko/tablewriter v0.0.5 + github.com/spf13/cobra v1.2.1 + // SPIRAL ------------ + github.com/spiral/endure v1.0.3 + github.com/spiral/errors v1.0.12 + github.com/spiral/goridge/v3 v3.2.1 + github.com/spiral/roadrunner/v2 v2.4.0-rc.1 + // --------------------- + github.com/stretchr/testify v1.7.0 + // SPIRAL -------------- + github.com/temporalio/roadrunner-temporal v1.0.9-beta.1 + // --------------------- + github.com/vbauerster/mpb/v5 v5.4.0 +) + +replace github.com/spiral/roadrunner/v2 => ../roadrunner <----- SAMPLE +``` + +3. Replace sample worker `psr-worker.php` with your application. You can do that by putting all `dev` env into the folder +with your app and replacing lines 21-24 of the `Dockerfile.local` with your app OR by replacing the sample worker. Also, do not forget to update `.rr-docker.yaml`. + +5. Next step is to build docker-compose: `docker-compose up`, that's it. After that, you'll have your app running in local dev env with RR. diff --git a/tests/composer.json b/dev/composer.json index fa5925b7..50178d1f 100644 --- a/tests/composer.json +++ b/dev/composer.json @@ -2,7 +2,7 @@ "minimum-stability": "beta", "prefer-stable": true, "require": { - "nyholm/psr7": "^1.4", + "nyholm/psr7": "^1.3", "spiral/roadrunner": "^2.0", "spiral/roadrunner-http": "^2.0", "temporal/sdk": ">=1.0", diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml new file mode 100644 index 00000000..673f7cd5 --- /dev/null +++ b/dev/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.7' + +services: + redis: + image: redis:6 + + roadrunner: + container_name: roadrunner + environment: + - GOGC=100 + ulimits: + nofile: + soft: 65000 + hard: 65000 + mem_limit: 1024 + cpus: 1 + restart: always + build: + context: . + dockerfile: Dockerfile.local + command: "serve -c /etc/rr-docker.yaml -d" + ports: + - "15395:15395" + - "6061:6061" + - "6001:6001" diff --git a/tests/psr-worker.php b/dev/psr-worker.php index db53eee2..fd8d672f 100644 --- a/tests/psr-worker.php +++ b/dev/psr-worker.php @@ -19,7 +19,7 @@ $psr7 = new RoadRunner\Http\PSR7Worker( while ($req = $psr7->waitRequest()) { try { $resp = new \Nyholm\Psr7\Response(); - $resp->getBody()->write(str_repeat("hello world", 1000)); + $resp->getBody()->write("hellllo world!!!!!"); $psr7->respond($resp); } catch (\Throwable $e) { diff --git a/doc/README.md b/doc/README.md deleted file mode 100644 index 709df603..00000000 --- a/doc/README.md +++ /dev/null @@ -1,21 +0,0 @@ -This is the drawio diagrams showing basic workflows inside RoadRunner 2.0 - -Simple HTTP workflow description: -![alt text](pool_workflow.svg) - -1. Allocate sync workers. When plugin starts (which use workers pool), then it allocates required number of processes - via `cmd.exec` command. - -2. When user send HTTP request to the RR2, HTTP plugin receive it and transfer to the workers pool `Exec/ExecWithContex` -method. And workers pool ask Worker watcher to get free worker. - -3. Workers watcher uses stack data structure under the hood and making POP operation to get first free worker. If there are -no workers in the `stack`, watcher waits for the specified via config (`allocate_timeout`) time. - -4. Stack returns free worker to the watcher. -5. Watcher returns that worker to the `pool`. -6. Pool invoke `Exec/ExecWithTimeout` method on the golang worker with provided request payload. -7. Golang worker send that request to the PHP worker via various set of transports (`pkg/transport` package). -8. PHP worker send back response to the golang worker (or error via stderr). -9. Golang worker return response payload to the pool. -10. Pool process this response and return answer to the user. diff --git a/doc/pool_workflow.drawio b/doc/pool_workflow.drawio deleted file mode 100644 index 3f74d0fc..00000000 --- a/doc/pool_workflow.drawio +++ /dev/null @@ -1 +0,0 @@ -<mxfile host="Electron" modified="2021-01-24T16:29:37.978Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.141 Electron/11.2.1 Safari/537.36" etag="8v25I0qLU_vMqqVrx7cN" version="14.1.8" type="device"><diagram id="8w40hpb1-UDYxj1ewOsN" name="Page-1">7Vxbd6M4Ev41OWfnoX0Qkrg8Jul09+zOTGc6vSfTjwRkm22MvCB37Pn1IwGydcExsTGO03HOiUEgLqqqr6o+lXwBr2fLj0U0n/5OE5JduE6yvIDvL1wXAMflX6Jl1bQ4jlO3TIo0qduUhrv0byJPbFoXaULKpq1uYpRmLJ3rjTHNcxIzrS0qCvqonzamWaI1zKMJsRru4iizW+/ThE3li3nh5sAnkk6m8tYeRvWRWSTPbl6lnEYJfVSa4M0FvC4oZfXWbHlNMjF8+sB82HJ0/WQFyVmXDn/hCPvfPk++/Ocm+HyPr5Z/TsfvvOYyP6Js0bzyhetl/IJXU37Im4itu3Q25wPiOv8tScG/CvL/BSmZPJHfcnNu865sJUewoIs8IeIZHH74cZoycjePYnH0kSuNuBGbZXwP8M0xzdmHaJZmQl+u6aJIqxv+Qfi4XUVZOsl5e0bG4k4/SMFSLqnLppnR+fr+6qjIN+Snk6XS1IzSR0JnhBUrfkpzFGNcd2mUFrmNFj+qGhA2GjBVpB9ItY0atZusL74RDN9oZPMMOYHQGliScE1tdmnBpnRC8yi72bRe6UO/Oec3KoaqGvD/EcZWjdlFC0Z1cZBlyv4S3Ucubna/KYfeL5tLVzsruZPzF1Z7if1v6sFNv2pPdtSE/2/CHooozUv+0r/TnD4l2pIrSkyeGr0GOaJiQtgT50lTFUP7pKYUJItY+kMHiTahN11vacqfea1hoedpGgYc2SCvUT9q083QnfVz7K9Orm32H8XrcNgS/8pVHgt9p8X3yv7+dfv5ln/ROSn4a9P8l1Zl/C164PCvKZA02ZiLjF/JNtpZmiS1rpIy/Tt6qK4nlGEuXr0aDHx1gd/vpR5PWpKFB2sn0jyFBtNtOPHOGTke0iUZ1HsH6sc7AJ2RDkL8ZsB1lA80VYaOxyU5jrZgS1vgmwbUQnE939e9RT8agAz5hyMHDSZv/7TOxledjdPV2fiaswEv3tnAvn3NQQGGRC7FxvEZ27hU4cNtnJs4QlDHYtiPjUPDxvEogEPZOHTsCEDcae3030RfwTvwdNm7vYgemaJ/B9DIw0MJ347+7iupi8G+j1g8bVGA5+VxaZZd04wWVV84HhMvjnl7yQr6nShHEj98cA7C4+55XQgNlwqbCErN63BjF2pa5x0tqwPWMA/qaPdJ6rSMbrT2uYO7WbejmwV9u9mq62VRRCvlhAbNthp8YOpeE71vtKe+Yr9m7lp2fpllNI6YleSVZwz4YBsS7AP4GHlHiecD3Y0gA1KOGMxDSwnaUecnFDaAGGhi6SmwQ7pvxyPgDyZuS9q/5ilLuSS5N+jTpycRCcatPt2LA/IwHsan+4GOq4HT4tOdFp9uGmB/Pr09bXpjajt5dXgqr36YzD3L6lQ69Y572vtzT6ukXvfEmjm6P/T7AV7fzKu8keerrKk7GA4jSyXcN+nX0oc8/NWl1I/0PYsz9Qfzu/CksA8UzFf40+flcv7pkrmusA/dF4X70MZ974yNHPZm5Dy0xrgnqwZoBAejRVE7SJ/V1Acf/B2GXO3dkiLlwyV076VYN+rbuvfiaoADA82PyKTiQ9cOPtrVwceHdcAONtT/CPwRaqEOZDHTOeKbNO4+8M1xJROxL74NAGZ2DHpH8qR65nJO81IkJxyL+L+p2FyUZ52h9CfeutjCmKHphwj0Xf2qbjAabNrHNuebJYn5pcTXfcqm11wQZMksHTh/nsic+2nlidrmfo7GE21JB88q0jhdkYWs1DuT6R/gID1C4A27PP6OHsdx+a6d0aAzdgnSyPrw+BAiA7t7mi1wDdoCgAHrQNzQEvivAgKjir4cF0R8recIryt0LETbii7EAxb2rMJPqh6uo08cyqmYA9UDADxCoakgCKmsJhhMW+yqoTsWxd8rR8r96Pbp5INCCEyCBLWFEIH7IOpgTxJCACc4df0ItI33tOsENBrS7UhDrqMItdtpaEi3Y1TRtXZzky46h9JhA6QHrqVNRn7wNZ0RuniN+QFwPd25tyYIshZwkAQB2umaNfDlNJqLzXFGlpdiuVtlLEmz+T7OorJMY8NYN9F8r5O++5SV7W+qOOzZVBU5t2G4bDvUmzuSQZPMHzCWcdRvbq38aSMdDQ7RGXYNEbTZJXN94BZtXcyyy5hRNQas4sVbWqZieRE/5YEyRmctQWK9ym/bksEjef7QWBAo1/4pOgMH9ft2sf6a2GuGXuX1tpR0V1TP0UJ0ESWUdcAwkJgAMDFcptAqhoeBLSizPKs3QSG70voLiUldG7IhYV+9YHyj2iN0bMHI9SbDCMZe0maJgV8mnZdEl4TEsJKjK+O5DyN2SFMnZW0hzXjcHBlg1D3PoDzlhMUO1ILHQi1kc0v2mO9MVhRZmEawdhpKCETnJK9bmuwleFIuL6/OXWrq7sXLHQOdQycwjNXxARo27EC+pUW32WKS5rxtXeparVJ+9bjqOUYsGUIbVuUvPAwCq7Kc+q0K6qj0g+QDX0gZFLYz1fYVxOfBCuNthrhXpSuSBnho5ojaS1kNtvn4NBE+7erwM6cYUWfeond/fpiN2ylncM423uP6cBA6ejlzP/OCAIhCaeWjzwfLepQByojsWYYtqxkOWiOMxV8rD1x9OsheHFf61Z+eUliMXtpCI2zzbzVdz990ldEoefXxL0BGjUQb4SOXGgwT/9q0whfy46fje5Br1MMGLUTcoHwPtjHs09ev4ket5k3yeBCU7QImA+qCmLT/HMJDwHHmyYmRZ7DW2FhkIxfDaIjVwoZCdCQheHb1gDXuL2lGa6sQdgZvuGuCdqJZJ+yH+jW6zzo54QgHys+Q6TpmTnkcmQyShL9KBlGadUBY1SRzmpNngW4bHOiAsUu1LMtfc8Z94K9jBCt+S6UKalE391iximczBB9pFuUT3rYllvzJRAbd3SJrjS+PJzM7lrn9JDxm5+nEVy0wLHmdJwQWoF7kxXc3PxNcQ+fm55bhzT8=</diagram></mxfile>
\ No newline at end of file diff --git a/doc/pool_workflow.svg b/doc/pool_workflow.svg deleted file mode 100644 index 1e043eaa..00000000 --- a/doc/pool_workflow.svg +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1200px" height="811px" viewBox="-0.5 -0.5 1200 811" content="<mxfile host="Electron" modified="2021-01-24T14:08:35.229Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.141 Electron/11.2.1 Safari/537.36" etag="XUF1gTxKr8mJfXGLcVd8" version="14.1.8" type="device"><diagram id="8w40hpb1-UDYxj1ewOsN" name="Page-1">7Vxbd6M4Ev41OWfnoX0Qkrg8Jul09+zOTGc6vSfTjwRkm22MvCB37Pn1IwGydcExsTGO03HOiUEgLqqqr6o+lXwBr2fLj0U0n/5OE5JduE6yvIDvL1wXAMflX6Jl1bQ4jlO3TIo0qduUhrv0byJPbFoXaULKpq1uYpRmLJ3rjTHNcxIzrS0qCvqonzamWaI1zKMJsRru4iizW+/ThE3li3nh5sAnkk6m8tYeRvWRWSTPbl6lnEYJfVSa4M0FvC4oZfXWbHlNMjF8+sB82HJ0/WQFyVmXDn/hCPvfPk++/Ocm+HyPr5Z/TsfvvOYyP6Js0bzyhetl/IJXU37Im4itu3Q25wPiOv8tScG/CvL/BSmZPJHfcnNu865sJUewoIs8IeIZHH74cZoycjePYnH0kSuNuBGbZXwP8M0xzdmHaJZmQl+u6aJIqxv+Qfi4XUVZOsl5e0bG4k4/SMFSLqnLppnR+fr+6qjIN+Snk6XS1IzSR0JnhBUrfkpzFGNcd2mUFrmNFj+qGhA2GjBVpB9ItY0atZusL74RDN9oZPMMOYHQGliScE1tdmnBpnRC8yi72bRe6UO/Oec3KoaqGvD/EcZWjdlFC0Z1cZBlyv4S3Ucubna/KYfeL5tLVzsruZPzF1Z7if1v6sFNv2pPdtSE/2/CHooozUv+0r/TnD4l2pIrSkyeGr0GOaJiQtgT50lTFUP7pKYUJItY+kMHiTahN11vacqfea1hoedpGgYc2SCvUT9q083QnfVz7K9Orm32H8XrcNgS/8pVHgt9p8X3yv7+dfv5ln/ROSn4a9P8l1Zl/C164PCvKZA02ZiLjF/JNtpZmiS1rpIy/Tt6qK4nlGEuXr0aDHx1gd/vpR5PWpKFB2sn0jyFBtNtOPHOGTke0iUZ1HsH6sc7AJ2RDkL8ZsB1lA80VYaOxyU5jrZgS1vgmwbUQnE939e9RT8agAz5hyMHDSZv/7TOxledjdPV2fiaswEv3tnAvn3NQQGGRC7FxvEZ27hU4cNtnJs4QlDHYtiPjUPDxvEogEPZOHTsCEDcae3030RfwTvwdNm7vYgemaJ/B9DIw0MJ347+7iupi8G+j1g8bVGA5+VxaZZd04wWVV84HhMvjnl7yQr6nShHEj98cA7C4+55XQgNlwqbCErN63BjF2pa5x0tqwPWMA/qaPdJ6rSMbrT2uYO7WbejmwV9u9mq62VRRCvlhAbNthp8YOpeE71vtKe+Yr9m7lp2fpllNI6YleSVZwz4YBsS7AP4GHlHiecD3Y0gA1KOGMxDSwnaUecnFDaAGGhi6SmwQ7pvxyPgDyZuS9q/5ilLuSS5N+jTpycRCcatPt2LA/IwHsan+4GOq4HT4tOdFp9uGmB/Pr09bXpjajt5dXgqr36YzD3L6lQ69Y572vtzT6ukXvfEmjm6P/T7AV7fzKu8keerrKk7GA4jSyXcN+nX0oc8/NWl1I/0PYsz9Qfzu/CksA8UzFf40+flcv7pkrmusA/dF4X70MZ974yNHPZm5Dy0xrgnqwZoBAejRVE7SJ/V1Acf/B2GXO3dkiLlwyV076VYN+rbuvfiaoADA82PyKTiQ9cOPtrVwceHdcAONtT/CPwRaqEOZDHTOeKbNO4+8M1xJROxL74NAGZ2DHpH8qR65nJO81IkJxyL+L+p2FyUZ52h9CfeutjCmKHphwj0Xf2qbjAabNrHNuebJYn5pcTXfcqm11wQZMksHTh/nsic+2nlidrmfo7GE21JB88q0jhdkYWs1DuT6R/gID1C4A27PP6OHsdx+a6d0aAzdgnSyPrw+BAiA7t7mi1wDdoCgAHrQNzQEvivAgKjir4cF0R8recIryt0LETbii7EAxb2rMJPqh6uo08cyqmYA9UDADxCoakgCKmsJhhMW+yqoTsWxd8rR8r96Pbp5INCCEyCBLWFEIH7IOpgTxJCACc4df0ItI33tOsENBrS7UhDrqMItdtpaEi3Y1TRtXZzky46h9JhA6QHrqVNRn7wNZ0RuniN+QFwPd25tyYIshZwkAQB2umaNfDlNJqLzXFGlpdiuVtlLEmz+T7OorJMY8NYN9F8r5O++5SV7W+qOOzZVBU5t2G4bDvUmzuSQZPMHzCWcdRvbq38aSMdDQ7RGXYNEbTZJXN94BZtXcyyy5hRNQas4sVbWqZieRE/5YEyRmctQWK9ym/bksEjef7QWBAo1/4pOgMH9ft2sf6a2GuGXuX1tpR0V1TP0UJ0ESWUdcAwkJgAMDFcptAqhoeBLSizPKs3QSG70voLiUldG7IhYV+9YHyj2iN0bMHI9SbDCMZe0maJgV8mnZdEl4TEsJKjK+O5DyN2SFMnZW0hzXjcHBlg1D3PoDzlhMUO1ILHQi1kc0v2mO9MVhRZmEawdhpKCETnJK9bmuwleFIuL6/OXWrq7sXLHQOdQycwjNXxARo27EC+pUW32WKS5rxtXeparVJ+9bjqOUYsGUIbVuUvPAwCq7Kc+q0K6qj0g+QDX0gZFLYz1fYVxOfBCuNthrhXpSuSBnho5ojaS1kNtvn4NBE+7erwM6cYUWfeond/fpiN2ylncM423uP6cBA6ejlzP/OCAIhCaeWjzwfLepQByojsWYYtqxkOWiOMxV8rD1x9OsheHFf61Z+eUliMXtpCI2zzbzVdz990ldEoefXxL0BGjUQb4SOXGgwT/9q0whfy46fje5Br1MMGLUTcoHwPtjHs09ev4ket5k3yeBCU7QImA+qCmLT/HMJDwHHmyYmRZ7DW2FhkIxfDaIjVwoZCdCQheHb1gDXuL2lGa6sQdgZvuGuCdqJZJ+yH+jW6zzo54QgHys+Q6TpmTnkcmQyShL9KBlGadUBY1SRzmpNngW4bHOiAsUu1LMtfc8Z94K9jBCt+S6UKalE391iximczBB9pFuUT3rYllvzJRAbd3SJrjS+PJzM7lrn9JDxm5+nEVy0wLHmdJwQWoF7kxXc3PxNcQ+fm55bhzT8=</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"><defs/><g><rect x="0" y="0" width="1199" height="810" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe flex-start; width: 1197px; height: 1px; padding-top: 7px; margin-left: 2px;"><div style="box-sizing: border-box; font-size: 0; text-align: left; "><div style="display: inline-block; font-size: 12px; font-family: Courier New; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><h1>Simple User request</h1></div></div></div></foreignObject><text x="2" y="19" fill="#000000" font-family="Courier New" font-size="12px">Simple User request</text></switch></g><path d="M 417.5 574 L 417.5 657.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 417.5 662.88 L 414 655.88 L 417.5 657.63 L 421 655.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 616px; margin-left: 296px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Give me sync worker (POP operation)</div></div></div></foreignObject><text x="296" y="620" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Give me sync worker (POP operation)</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 617px; margin-left: 418px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">3</div></div></div></foreignObject><text x="418" y="620" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">3</text></switch></g><path d="M 492.5 514 L 492.5 430.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 492.5 425.12 L 496 432.12 L 492.5 430.37 L 489 432.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 464px; margin-left: 493px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">5</div></div></div></foreignObject><text x="493" y="468" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">5</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 465px; margin-left: 535px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Get worker</div></div></div></foreignObject><text x="535" y="468" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Get worker</text></switch></g><rect x="380" y="514" width="150" height="60" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 544px; margin-left: 381px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Workers Watcher</div></div></div></foreignObject><text x="455" y="548" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Workers Watcher</text></switch></g><path d="M 280 424 L 280 544 L 373.63 544" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 378.88 544 L 371.88 547.5 L 373.63 544 L 371.88 540.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 515px; margin-left: 202px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Allocate sync workers</div></div></div></foreignObject><text x="202" y="518" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Allocate sync workers</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 514px; margin-left: 280px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">1</div></div></div></foreignObject><text x="280" y="518" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">1</text></switch></g><rect x="230" y="384" width="100" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 404px; margin-left: 231px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Initialize</div></div></div></foreignObject><text x="280" y="408" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Initialize</text></switch></g><path d="M 417.5 424 L 417.5 507.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 417.5 512.88 L 414 505.88 L 417.5 507.63 L 421 505.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 464px; margin-left: 352px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Give me SyncWorker</div></div></div></foreignObject><text x="352" y="467" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Give me SyncWorker</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 464px; margin-left: 418px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">2</div></div></div></foreignObject><text x="418" y="468" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">2</text></switch></g><path d="M 530 414 L 700.63 414" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 705.88 414 L 698.88 417.5 L 700.63 414 L 698.88 410.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 415px; margin-left: 618px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">6</div></div></div></foreignObject><text x="618" y="418" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">6</text></switch></g><path d="M 492.5 384 L 483 384 L 483 324 L 520 324 L 520 83 L 468.87 83" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 463.62 83 L 470.62 79.5 L 468.87 83 L 470.62 86.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 224px; margin-left: 521px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">10</div></div></div></foreignObject><text x="521" y="227" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">10</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 225px; margin-left: 597px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">Send response to the user</div></div></div></foreignObject><text x="597" y="228" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">Send response to the user</text></switch></g><rect x="380" y="384" width="150" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 404px; margin-left: 381px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Exec/ExecWithContext</div></div></div></foreignObject><text x="455" y="408" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Exec/ExecWithContext</text></switch></g><path d="M 492.5 664 L 492.5 624 L 492.5 580.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 492.5 575.12 L 496 582.12 L 492.5 580.37 L 489 582.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 616px; margin-left: 494px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">4</div></div></div></foreignObject><text x="494" y="620" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">4</text></switch></g><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 617px; margin-left: 610px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">I have free workers, here you are</div></div></div></foreignObject><text x="610" y="620" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">I have free workers, here you are</text></switch></g><rect x="380" y="664" width="150" height="60" fill="#d5e8d4" stroke="#82b366" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 694px; margin-left: 381px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Stack with workers</div></div></div></foreignObject><text x="455" y="698" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Stack with workers</text></switch></g><path d="M 707 394 L 536.37 394" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 531.12 394 L 538.12 390.5 L 536.37 394 L 538.12 397.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 394px; margin-left: 618px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">9</div></div></div></foreignObject><text x="618" y="397" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">9</text></switch></g><rect x="707" y="384" width="163" height="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 161px; height: 1px; padding-top: 404px; margin-left: 708px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Exec/ExecWithTimeout</div></div></div></foreignObject><text x="789" y="408" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Exec/ExecWithTimeout</text></switch></g><path d="M 450 289.5 L 460 289.5 L 460 364.5 L 470.5 364.5 L 455 383.5 L 439.5 364.5 L 450 364.5 Z" fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="455" cy="84.5" rx="7.5" ry="7.5" fill="#ffffff" stroke="#000000" pointer-events="all"/><path d="M 455 92 L 455 117 M 455 97 L 440 97 M 455 97 L 470 97 M 455 117 L 440 137 M 455 117 L 470 137" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 144px; margin-left: 455px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">User request</div></div></div></foreignObject><text x="455" y="156" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">User...</text></switch></g><rect x="607" y="426" width="198" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 435px; margin-left: 706px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Send request to the worker</div></div></div></foreignObject><text x="706" y="438" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Send request to the worker</text></switch></g><rect x="618" y="368" width="125" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 377px; margin-left: 681px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Receive response</div></div></div></foreignObject><text x="681" y="380" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Receive response</text></switch></g><ellipse cx="125" cy="404" rx="11" ry="11" fill="#000000" stroke="#ff0000" pointer-events="all"/><path d="M 140 404 L 227.76 404" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 219.88 408.5 L 228.88 404 L 219.88 399.5" fill="none" stroke="#ff0000" stroke-miterlimit="10" pointer-events="all"/><rect x="45" y="371" width="161" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 380px; margin-left: 126px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Plugin Initialization</div></div></div></foreignObject><text x="126" y="383" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Plugin Initialization</text></switch></g><path d="M 870 414 L 983.63 414" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 988.88 414 L 981.88 417.5 L 983.63 414 L 981.88 410.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 413px; margin-left: 930px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">7</div></div></div></foreignObject><text x="930" y="416" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">7</text></switch></g><path d="M 990 394 L 876.37 394" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 871.12 394 L 878.12 390.5 L 876.37 394 L 878.12 397.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 394px; margin-left: 931px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">8</div></div></div></foreignObject><text x="931" y="397" fill="#000000" font-family="Jetbrains Mono" font-size="11px" text-anchor="middle">8</text></switch></g><rect x="990" y="384" width="100" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 404px; margin-left: 991px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Worker</div></div></div></foreignObject><text x="1040" y="408" fill="#333333" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Worker</text></switch></g><rect x="893" y="426" width="96" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 435px; margin-left: 941px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Exec payload</div></div></div></foreignObject><text x="941" y="438" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Exec payload</text></switch></g><rect x="873" y="366" width="125" height="17" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 375px; margin-left: 936px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Reveive response</div></div></div></foreignObject><text x="936" y="378" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Reveive response</text></switch></g><rect x="401" y="255" width="108" height="34" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 106px; height: 1px; padding-top: 272px; margin-left: 402px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">HTTP plugin</div></div></div></foreignObject><text x="455" y="276" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">HTTP plugin</text></switch></g><path d="M 450 157.5 L 460 157.5 L 460 235.5 L 470.5 235.5 L 455 254.5 L 439.5 235.5 L 450 235.5 Z" fill="none" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><rect x="490" y="364" width="40" height="20" fill="none" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 374px; margin-left: 491px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Pool</div></div></div></foreignObject><text x="510" y="378" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Pool</text></switch></g><rect x="770" y="364" width="100" height="20" fill="none" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 374px; margin-left: 771px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Golang Worker</div></div></div></foreignObject><text x="820" y="378" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">Golang Worker</text></switch></g><rect x="1006" y="364" width="84" height="20" fill="none" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 82px; height: 1px; padding-top: 374px; margin-left: 1007px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Jetbrains Mono; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">PHP worker</div></div></div></foreignObject><text x="1048" y="378" fill="#000000" font-family="Jetbrains Mono" font-size="12px" text-anchor="middle">PHP worker</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg>
\ No newline at end of file diff --git a/events/docs/events.md b/events/docs/events.md deleted file mode 100644 index 37059b25..00000000 --- a/events/docs/events.md +++ /dev/null @@ -1,136 +0,0 @@ -## RoadRunner Events bus - -RR events bus might be useful when one plugin raises some event on which another plugin should react. For example, -plugins like sentry might log errors from the `http` plugin. - -## Simple subscription - -Events bus supports wildcard subscriptions on the events as well as the direct subscriptions on the particular event. - -Let's have a look at the simple example: - -```go -package foo - -import ( - "github.com/spiral/roadrunner/v2/events" -) - -func foo() { - eh, id := events.Bus() - defer eh.Unsubscribe(id) - - ch := make(chan events.Event, 100) - err := eh.SubscribeP(id, "http.EventJobOK", ch) - if err != nil { - panic(err) - } - - eh.Send(events.NewEvent(events.EventJobOK, "http", "foo")) - evt := <-ch - // evt.Message() -> "foo" - // evt.Plugin() -> "http" - // evt.Type().String() -> "EventJobOK" -} -``` - -Here: -1. `eh, id := events.Bus()` get the instance (it's global) of the events bus. Make sure to unsubscribe event handler when you don't need it anymore: `eh.Unsubscribe(id)`. -2. `ch := make(chan events.Event, 100)` create an events channel. -3. `err := eh.SubscribeP(id, "http.EventJobOK", ch)` subscribe to the events which fits your pattern (`http.EventJobOK`). -4. `eh.Send(events.NewEvent(events.EventJobOK, "http", "foo"))` emit event from the any plugin. -5. `evt := <-ch` get the event. - -Notes: -1. If you use only `eh.Send` events bus function, you don't need to unsubscribe, so, you may simplify the declaration to the `eh, _ := events.Bus()`. - -## Wildcards - -As mentioned before, RR events bus supports wildcards subscriptions, like: `*.SomeEvent`, `http.*`, `http.Some*`, `*`. -Let's have a look at the next sample of code: - -```go -package foo - -import ( - "github.com/spiral/roadrunner/v2/events" -) - -func foo() { - eh, id := events.Bus() - defer eh.Unsubscribe(id) - - ch := make(chan events.Event, 100) - err := eh.SubscribeP(id, "http.*", ch) - if err != nil { - panic(err) - } - - eh.Send(events.NewEvent(events.EventJobOK, "http", "foo")) - evt := <-ch - // evt.Message() -> "foo" - // evt.Plugin() -> "http" - // evt.Type().String() -> "EventJobOK" -} -``` - -One change between these samples is in the `SubscribeP` pattern: `err := eh.SubscribeP(id, "http.*", ch)`. Here we used `http.*` instead of `http.EventJobOK`. - -You also have the access to the event message, plugin and type. Message is a custom, user-defined message to log or to show to the subscriber. Plugin is a source plugin who raised this event. And the event type - is your custom or RR event type. - - -## How to implement custom event - -Event type is a `fmt.Stringer`. That means, that your custom event type should implement this interface. Let's have a look at how to do that: - -```go -package foo - -type MySuperEvent uint32 - -const ( - EventHTTPError MySuperEvent = iota -) - -func (mse MySuperEvent) String() string { - switch mse { - case EventHTTPError: - return "EventHTTPError" - default: - return "UnknownEventType" - } -} -``` - -Here we defined a custom type - `MySuperEvent`. For sure, it might have any name you want and represent for example some domain field like `WorkersPoolEvent` represents RR sync.pool events. Then you need to implement a `fmt.Stringer` on your custom event type. -Next you need to create an enum with the actual events and that's it. - -How to use that: -```go -package foo - -import ( - "github.com/spiral/roadrunner/v2/events" -) - -func foo() { - eh, id := events.Bus() - defer eh.Unsubscribe(id) - - ch := make(chan events.Event, 100) - err := eh.SubscribeP(id, "http.EventHTTPError", ch) - if err != nil { - panic(err) - } - - // first arg of the NewEvent method is fmt.Stringer - eh.Send(events.NewEvent(EventHTTPError, "http", "foo")) - evt := <-ch - // evt.Message() -> "foo" - // evt.Plugin() -> "http" - // evt.Type().String() -> "EventHTTPError" -} -``` - -Important note: you don't need to import your custom event types into the subscriber. You only need to know the name of that event and pass a string to the subscriber. - diff --git a/events/events.go b/events/events.go deleted file mode 100644 index 42519637..00000000 --- a/events/events.go +++ /dev/null @@ -1,61 +0,0 @@ -package events - -type EventType uint32 - -const ( - // EventWorkerConstruct thrown when new worker is spawned. - EventWorkerConstruct EventType = iota - // EventWorkerDestruct thrown after worker destruction. - EventWorkerDestruct - // EventWorkerProcessExit triggered on process wait exit - EventWorkerProcessExit - // EventNoFreeWorkers triggered when there are no free workers in the stack and timeout for worker allocate elapsed - EventNoFreeWorkers - // EventMaxMemory caused when worker consumes more memory than allowed. - EventMaxMemory - // EventTTL thrown when worker is removed due TTL being reached. TTL defines maximum time worker is allowed to live (seconds) - EventTTL - // EventIdleTTL triggered when worker spends too much time at rest. - EventIdleTTL - // EventExecTTL triggered when worker spends too much time doing the task (max_execution_time). - EventExecTTL - // EventWorkerError triggered after WorkerProcess. Except payload to be error. - EventWorkerError - // EventWorkerStderr is the worker standard error output - EventWorkerStderr - // EventWorkerWaitExit is the worker exit event - EventWorkerWaitExit - // EventWorkerStopped triggered when worker gracefully stopped - EventWorkerStopped -) - -func (et EventType) String() string { - switch et { - case EventWorkerProcessExit: - return "EventWorkerProcessExit" - case EventWorkerConstruct: - return "EventWorkerConstruct" - case EventWorkerDestruct: - return "EventWorkerDestruct" - case EventNoFreeWorkers: - return "EventNoFreeWorkers" - case EventMaxMemory: - return "EventMaxMemory" - case EventTTL: - return "EventTTL" - case EventIdleTTL: - return "EventIdleTTL" - case EventExecTTL: - return "EventExecTTL" - case EventWorkerError: - return "EventWorkerError" - case EventWorkerStderr: - return "EventWorkerStderr" - case EventWorkerWaitExit: - return "EventWorkerWaitExit" - case EventWorkerStopped: - return "EventWorkerStopped" - default: - return "UnknownEventType" - } -} diff --git a/events/events_test.go b/events/events_test.go deleted file mode 100644 index 62ddd903..00000000 --- a/events/events_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package events - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestEvenHandler(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - ch := make(chan Event, 100) - err := eh.SubscribeP(id, "http.EventWorkerError", ch) - require.NoError(t, err) - - eh.Send(NewEvent(EventWorkerError, "http", "foo")) - - evt := <-ch - require.Equal(t, "foo", evt.Message()) - require.Equal(t, "http", evt.Plugin()) - require.Equal(t, "EventWorkerError", evt.Type().String()) - - eh.Unsubscribe(id) -} - -func TestEvenHandler2(t *testing.T) { - eh, id := Bus() - eh2, id2 := Bus() - defer eh.Unsubscribe(id) - defer eh2.Unsubscribe(id2) - - ch := make(chan Event, 100) - ch2 := make(chan Event, 100) - err := eh2.SubscribeP(id2, "http.EventWorkerError", ch) - require.NoError(t, err) - - err = eh.SubscribeP(id, "http.EventWorkerError", ch2) - require.NoError(t, err) - - eh.Send(NewEvent(EventWorkerError, "http", "foo")) - - evt := <-ch2 - require.Equal(t, "foo", evt.Message()) - require.Equal(t, "http", evt.Plugin()) - require.Equal(t, "EventWorkerError", evt.Type().String()) - - l := eh.Len() - require.Equal(t, uint(2), l) - - eh.Unsubscribe(id) - time.Sleep(time.Second) - - l = eh.Len() - require.Equal(t, uint(1), l) - - eh2.Unsubscribe(id2) - time.Sleep(time.Second) - - l = eh.Len() - require.Equal(t, uint(0), l) - - eh.Unsubscribe(id) - eh2.Unsubscribe(id2) -} - -func TestEvenHandler3(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - ch := make(chan Event, 100) - err := eh.SubscribeP(id, "EventWorkerError", ch) - require.Error(t, err) - - eh.Unsubscribe(id) -} - -func TestEvenHandler4(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - err := eh.SubscribeP(id, "EventWorkerError", nil) - require.Error(t, err) - - eh.Unsubscribe(id) -} - -func TestEvenHandler5(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - ch := make(chan Event, 100) - err := eh.SubscribeP(id, "http.EventWorkerError", ch) - require.NoError(t, err) - - eh.Send(NewEvent(EventWorkerError, "http", "foo")) - - evt := <-ch - require.Equal(t, "foo", evt.Message()) - require.Equal(t, "http", evt.Plugin()) - require.Equal(t, "EventWorkerError", evt.Type().String()) - - eh.Unsubscribe(id) -} - -type MySuperEvent uint32 - -const ( - // EventHTTPError represents success unary call response - EventHTTPError MySuperEvent = iota -) - -func (mse MySuperEvent) String() string { - switch mse { - case EventHTTPError: - return "EventHTTPError" - default: - return "UnknownEventType" - } -} - -func TestEvenHandler6(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - ch := make(chan Event, 100) - err := eh.SubscribeP(id, "http.EventHTTPError", ch) - require.NoError(t, err) - - eh.Send(NewEvent(EventHTTPError, "http", "foo")) - - evt := <-ch - require.Equal(t, "foo", evt.Message()) - require.Equal(t, "http", evt.Plugin()) - require.Equal(t, "EventHTTPError", evt.Type().String()) - - eh.Unsubscribe(id) -} - -func TestEvenHandler7(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - ch := make(chan Event, 100) - err := eh.SubscribeAll(id, ch) - require.NoError(t, err) - - eh.Send(NewEvent(EventHTTPError, "http", "foo")) - - evt := <-ch - require.Equal(t, "foo", evt.Message()) - require.Equal(t, "http", evt.Plugin()) - require.Equal(t, "EventHTTPError", evt.Type().String()) - - eh.Unsubscribe(id) -} - -func TestEvenHandler8(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - err := eh.SubscribeAll(id, nil) - require.Error(t, err) - - eh.Unsubscribe(id) -} - -func TestEvenHandler9(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - ch := make(chan Event, 100) - err := eh.SubscribeP(id, "http.EventWorkerError", ch) - require.NoError(t, err) - - eh.Send(NewEvent(EventWorkerError, "http", "foo")) - - evt := <-ch - require.Equal(t, "foo", evt.Message()) - require.Equal(t, "http", evt.Plugin()) - require.Equal(t, "EventWorkerError", evt.Type().String()) - - eh.UnsubscribeP(id, "http.EventWorkerError") - - eh.Send(NewEvent(EventWorkerError, "http", "foo")) - - select { - case <-ch: - require.Fail(t, "should not read any events") - default: - return - } -} - -func TestEvenHandler10(t *testing.T) { - eh, id := Bus() - defer eh.Unsubscribe(id) - - ch := make(chan Event, 100) - err := eh.SubscribeP(id, "http.EventHTTPError", ch) - require.NoError(t, err) - err = eh.SubscribeP(id, "http.Foo", ch) - require.NoError(t, err) - err = eh.SubscribeP(id, "http.Foo2", ch) - require.NoError(t, err) - err = eh.SubscribeP(id, "http.Foo2", ch) - require.NoError(t, err) - - eh.Send(NewEvent(EventHTTPError, "http", "foo")) - - evt := <-ch - require.Equal(t, "foo", evt.Message()) - require.Equal(t, "http", evt.Plugin()) - require.Equal(t, "EventHTTPError", evt.Type().String()) - - eh.Unsubscribe(id) -} diff --git a/events/eventsbus.go b/events/eventsbus.go deleted file mode 100644 index a2a2c859..00000000 --- a/events/eventsbus.go +++ /dev/null @@ -1,170 +0,0 @@ -package events - -import ( - "fmt" - "strings" - "sync" - - "github.com/spiral/errors" -) - -type sub struct { - pattern string - w *wildcard - events chan<- Event -} - -type eventsBus struct { - sync.RWMutex - subscribers sync.Map - internalEvCh chan Event - stop chan struct{} -} - -func newEventsBus() *eventsBus { - return &eventsBus{ - internalEvCh: make(chan Event, 100), - stop: make(chan struct{}), - } -} - -/* -http.* <- -*/ - -// SubscribeAll for all RR events -// returns subscriptionID -func (eb *eventsBus) SubscribeAll(subID string, ch chan<- Event) error { - if ch == nil { - return errors.Str("nil channel provided") - } - - subIDTr := strings.Trim(subID, " ") - - if subIDTr == "" { - return errors.Str("subscriberID can't be empty") - } - - return eb.subscribe(subID, "*", ch) -} - -// SubscribeP pattern like "pluginName.EventType" -func (eb *eventsBus) SubscribeP(subID string, pattern string, ch chan<- Event) error { - if ch == nil { - return errors.Str("nil channel provided") - } - - subIDTr := strings.Trim(subID, " ") - patternTr := strings.Trim(pattern, " ") - - if subIDTr == "" || patternTr == "" { - return errors.Str("subscriberID or pattern can't be empty") - } - - return eb.subscribe(subID, pattern, ch) -} - -func (eb *eventsBus) Unsubscribe(subID string) { - eb.subscribers.Delete(subID) -} - -func (eb *eventsBus) UnsubscribeP(subID, pattern string) { - if sb, ok := eb.subscribers.Load(subID); ok { - eb.Lock() - defer eb.Unlock() - - sbArr := sb.([]*sub) - - for i := 0; i < len(sbArr); i++ { - if sbArr[i].pattern == pattern { - sbArr[i] = sbArr[len(sbArr)-1] - sbArr = sbArr[:len(sbArr)-1] - // replace with new array - eb.subscribers.Store(subID, sbArr) - return - } - } - } -} - -// Send sends event to the events bus -func (eb *eventsBus) Send(ev Event) { - // do not accept nil events - if ev == nil { - return - } - - eb.internalEvCh <- ev -} - -func (eb *eventsBus) Len() uint { - var ln uint - - eb.subscribers.Range(func(key, value interface{}) bool { - ln++ - return true - }) - - return ln -} - -func (eb *eventsBus) subscribe(subID string, pattern string, ch chan<- Event) error { - eb.Lock() - defer eb.Unlock() - w, err := newWildcard(pattern) - if err != nil { - return err - } - - if sb, ok := eb.subscribers.Load(subID); ok { - // at this point we are confident that sb is a []*sub type - subArr := sb.([]*sub) - subArr = append(subArr, &sub{ - pattern: pattern, - w: w, - events: ch, - }) - - eb.subscribers.Store(subID, subArr) - - return nil - } - - subArr := make([]*sub, 0, 1) - subArr = append(subArr, &sub{ - pattern: pattern, - w: w, - events: ch, - }) - - eb.subscribers.Store(subID, subArr) - - return nil -} - -func (eb *eventsBus) handleEvents() { - for { //nolint:gosimple - select { - case ev := <-eb.internalEvCh: - // http.WorkerError for example - wc := fmt.Sprintf("%s.%s", ev.Plugin(), ev.Type().String()) - - eb.subscribers.Range(func(key, value interface{}) bool { - vsub := value.([]*sub) - - for i := 0; i < len(vsub); i++ { - if vsub[i].w.match(wc) { - select { - case vsub[i].events <- ev: - return true - default: - return true - } - } - } - - return true - }) - } - } -} diff --git a/events/init.go b/events/init.go deleted file mode 100644 index 25e92fc5..00000000 --- a/events/init.go +++ /dev/null @@ -1,20 +0,0 @@ -package events - -import ( - "sync" - - "github.com/google/uuid" -) - -var evBus *eventsBus -var onInit = &sync.Once{} - -func Bus() (*eventsBus, string) { - onInit.Do(func() { - evBus = newEventsBus() - go evBus.handleEvents() - }) - - // return events bus with subscriberID - return evBus, uuid.NewString() -} diff --git a/events/types.go b/events/types.go deleted file mode 100644 index 806e81ce..00000000 --- a/events/types.go +++ /dev/null @@ -1,54 +0,0 @@ -package events - -import ( - "fmt" -) - -type EventBus interface { - SubscribeAll(subID string, ch chan<- Event) error - SubscribeP(subID string, pattern string, ch chan<- Event) error - Unsubscribe(subID string) - UnsubscribeP(subID, pattern string) - Len() uint - Send(ev Event) -} - -type Event interface { - Type() fmt.Stringer - Plugin() string - Message() string -} - -type event struct { - // event typ - typ fmt.Stringer - // plugin - plugin string - // message - message string -} - -// NewEvent initializes new event -func NewEvent(t fmt.Stringer, plugin string, message string) *event { - if t.String() == "" || plugin == "" { - return nil - } - - return &event{ - typ: t, - plugin: plugin, - message: message, - } -} - -func (r *event) Type() fmt.Stringer { - return r.typ -} - -func (r *event) Message() string { - return r.message -} - -func (r *event) Plugin() string { - return r.plugin -} diff --git a/events/wildcard.go b/events/wildcard.go deleted file mode 100644 index b4c28ae1..00000000 --- a/events/wildcard.go +++ /dev/null @@ -1,43 +0,0 @@ -package events - -import ( - "strings" - - "github.com/spiral/errors" -) - -type wildcard struct { - prefix string - suffix string -} - -func newWildcard(pattern string) (*wildcard, error) { - // Normalize - origin := strings.ToLower(pattern) - i := strings.IndexByte(origin, '*') - - /* - http.* - * - *.WorkerError - */ - if i == -1 { - dotI := strings.IndexByte(pattern, '.') - - if dotI == -1 { - // http.SuperEvent - return nil, errors.Str("wrong wildcard, no * or . Usage: http.Event or *.Event or http.*") - } - - return &wildcard{origin[0:dotI], origin[dotI+1:]}, nil - } - - // pref: http. - // suff: * - return &wildcard{origin[0:i], origin[i+1:]}, nil -} - -func (w wildcard) match(s string) bool { - s = strings.ToLower(s) - return len(s) >= len(w.prefix)+len(w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) -} diff --git a/events/wildcard_test.go b/events/wildcard_test.go deleted file mode 100644 index dfa65e63..00000000 --- a/events/wildcard_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package events - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestWildcard(t *testing.T) { - w, err := newWildcard("http.*") - assert.NoError(t, err) - assert.True(t, w.match("http.SuperEvent")) - assert.False(t, w.match("https.SuperEvent")) - assert.False(t, w.match("")) - assert.False(t, w.match("*")) - assert.False(t, w.match("****")) - assert.True(t, w.match("http.****")) - - // *.* -> * - w, err = newWildcard("*") - assert.NoError(t, err) - assert.True(t, w.match("http.SuperEvent")) - assert.True(t, w.match("https.SuperEvent")) - assert.True(t, w.match("")) - assert.True(t, w.match("*")) - assert.True(t, w.match("****")) - assert.True(t, w.match("http.****")) - - w, err = newWildcard("*.WorkerError") - assert.NoError(t, err) - assert.False(t, w.match("http.SuperEvent")) - assert.False(t, w.match("https.SuperEvent")) - assert.False(t, w.match("")) - assert.False(t, w.match("*")) - assert.False(t, w.match("****")) - assert.False(t, w.match("http.****")) - assert.True(t, w.match("http.WorkerError")) - - w, err = newWildcard("http.WorkerError") - assert.NoError(t, err) - assert.False(t, w.match("http.SuperEvent")) - assert.False(t, w.match("https.SuperEvent")) - assert.False(t, w.match("")) - assert.False(t, w.match("*")) - assert.False(t, w.match("****")) - assert.False(t, w.match("http.****")) - assert.True(t, w.match("http.WorkerError")) - - w, err = newWildcard("http.Worker*") - assert.NoError(t, err) - assert.True(t, w.match("http.WorkerFoo")) - assert.False(t, w.match("h*.SuperEvent")) - assert.False(t, w.match("h*.Worker")) -} @@ -1,33 +1,140 @@ -module github.com/spiral/roadrunner/v2 +module github.com/spiral/roadrunner-binary/v2 go 1.17 require ( - github.com/google/uuid v1.3.0 - github.com/json-iterator/go v1.1.12 - github.com/shirou/gopsutil v3.21.11+incompatible - // spiral + github.com/buger/goterm v1.0.1 + github.com/dustin/go-humanize v1.0.0 + github.com/fatih/color v1.13.0 + github.com/joho/godotenv v1.4.0 + github.com/mattn/go-runewidth v0.0.13 + github.com/olekukonko/tablewriter v0.0.5 + github.com/roadrunner-server/api/v2 v2.0.0-rc.1 + github.com/spf13/cobra v1.3.0 + github.com/spiral/endure v1.1.0 github.com/spiral/errors v1.0.12 github.com/spiral/goridge/v3 v3.2.7 - // spiral + github.com/spiral/roadrunner-plugins/v2 v2.7.1 + github.com/spiral/roadrunner/v2 v2.7.1 github.com/stretchr/testify v1.7.0 - go.uber.org/multierr v1.7.0 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + github.com/temporalio/roadrunner-temporal v1.1.0 + github.com/vbauerster/mpb/v5 v5.4.0 ) require ( + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/aws/aws-sdk-go-v2 v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.7.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.9.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.1.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.6.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sqs v1.15.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.13.0 // indirect + github.com/aws/smithy-go v1.9.1 // indirect + github.com/beanstalkd/go-beanstalk v0.1.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect + github.com/caddyserver/certmagic v0.15.2 // indirect + github.com/cenkalti/backoff/v4 v4.1.2 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/emicklei/proto v1.9.1 // indirect + github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-redis/redis/v8 v8.11.4 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.1.0 // indirect + github.com/goccy/go-json v0.9.1 // indirect + github.com/gofiber/fiber/v2 v2.24.0 // indirect + github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/gogo/status v1.1.0 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect + github.com/hashicorp/go-version v1.4.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.14.1 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/libdns/libdns v0.2.1 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mholt/acmez v1.0.1 // indirect + github.com/miekg/dns v1.1.45 // indirect + github.com/minio/highwayhash v1.0.2 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nats-io/jwt/v2 v2.1.0 // indirect + github.com/nats-io/nats.go v1.13.0 // indirect + github.com/nats-io/nkeys v0.3.0 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/newrelic/go-agent/v3 v3.15.2 // indirect + github.com/pborman/uuid v1.2.1 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spiral/tcplisten v1.0.0 + github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/rabbitmq/amqp091-go v1.2.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/robfig/cron v1.2.0 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/spf13/afero v1.8.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.10.1 // indirect + github.com/spiral/sdk-go v1.12.0 // indirect + github.com/spiral/sdk-go/contrib/tally v0.0.0-20211210212330-614642034d01 // indirect + github.com/spiral/tcplisten v1.0.0 // indirect + github.com/stretchr/objx v0.3.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.9 // indirect github.com/tklauser/numcpus v0.3.0 // indirect + github.com/twmb/murmur3 v1.1.6 // indirect + github.com/uber-go/tally/v4 v4.1.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.32.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect + go.etcd.io/bbolt v1.3.6 // indirect + go.temporal.io/api v1.7.0 // indirect go.uber.org/atomic v1.9.0 // indirect - go.uber.org/zap v1.20.0 + go.uber.org/multierr v1.7.0 // indirect + go.uber.org/zap v1.20.0 // indirect + golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect + golang.org/x/mod v0.5.1 // indirect + golang.org/x/net v0.0.0-20220111093109-d55c255bac03 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect + golang.org/x/tools v0.1.8 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2 // indirect + google.golang.org/grpc v1.43.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/ini.v1 v1.66.2 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) @@ -1,102 +1,1152 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go-v2 v1.12.0 h1:z5bijqy+eXLK/QqF6eQcwCN2qw1k+m9OUDicqCZygu0= +github.com/aws/aws-sdk-go-v2 v1.12.0/go.mod h1:tWhQI5N5SiMawto3uMAQJU5OUN/1ivhDDHq7HTsJvZ0= +github.com/aws/aws-sdk-go-v2/config v1.12.0 h1:WOhIzj5HdixjlvQ4SLYAOk6OUUsuu88RwcsTzexa9cg= +github.com/aws/aws-sdk-go-v2/config v1.12.0/go.mod h1:GQONFVSDdG6RRho1C730SGNyDhS1kSTnxpOE76ptBqo= +github.com/aws/aws-sdk-go-v2/credentials v1.7.0 h1:KFuKwPs7i5SE5a0LxqAxz75qxSjr2HnHnhu0UPGlvpM= +github.com/aws/aws-sdk-go-v2/credentials v1.7.0/go.mod h1:Kmq64kahHJtXfmnEwnvRKeNjLBqkdP++Itln9BmQerE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.9.0 h1:fPq3oloONbHaA0O8KX/KYUQk7pG9JjKBwYQvQsQDK84= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.9.0/go.mod h1:19SxQ+9zANyJCnNaoF3ovl8bFil4TaqCYEDdqNGKM+A= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.3 h1:YPNiEXnuWdkpNOwBFHhcLwkSmewwQRcPFO9dHmxU0qg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.3/go.mod h1:L72JSFj9OwHwyukeuKFFyTj6uFWE4AjB0IQp97bd9Lc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.1.0 h1:ArRd27pSm66f7cCBDPS77wvxiS4IRjFatpzVBD7Aojc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.1.0/go.mod h1:KdVvdk4gb7iatuHZgIkIqvJlWHBtjCJLUtD/uO/FkWw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.3 h1:fmGqMNlFTHr9Y48qmYYv2qIo+TAsST3qZa2d1HcwBeo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.3/go.mod h1:N4dv+zawriMFZBO/6UKg3zt+XO6xWOQo1neAA0lFbo4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.6.0 h1:rwE0kWa5qm0yEoNPwC3zhrt1tFVXTmkWRlUxLayAwyc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.6.0/go.mod h1:wTgFkG6t7jS/6Y0SILXwfspV3IXowb6ngsAlSajW0Kc= +github.com/aws/aws-sdk-go-v2/service/sqs v1.15.0 h1:XqJ0gfT7oWQtLoig+sNiqBYJPOAGV7bTsSxDR2NJsBw= +github.com/aws/aws-sdk-go-v2/service/sqs v1.15.0/go.mod h1:z9jr/hWntzJNl1ISnw27SCKa/bnI9Pm0u0OgEKxrE2Y= +github.com/aws/aws-sdk-go-v2/service/sso v1.8.0 h1:X77LUt6Djy3Z02r6tW7Z+4FNr6GCnEG54EXfskc19M4= +github.com/aws/aws-sdk-go-v2/service/sso v1.8.0/go.mod h1:AB6v3BedyhVRIbPQbJnUsBmtup2pFiikpp5n3YyB6Ac= +github.com/aws/aws-sdk-go-v2/service/sts v1.13.0 h1:n8+dZMOvwkGtmhub8B2wYvRHut45/NB7DeNhNcUnBpg= +github.com/aws/aws-sdk-go-v2/service/sts v1.13.0/go.mod h1:jQto17aC9pJ6xRa1g29uXZhbcS6qNT3PSnKfPShq4sY= +github.com/aws/smithy-go v1.9.1 h1:5vetTooLk4hPWV8q6ym6+lXKAT1Urnm49YkrRKo2J8o= +github.com/aws/smithy-go v1.9.1/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/beanstalkd/go-beanstalk v0.1.0 h1:IiNwYbAoVBDs5xEOmleGoX+DRD3Moz99EpATbl8672w= +github.com/beanstalkd/go-beanstalk v0.1.0/go.mod h1:/G8YTyChOtpOArwLTQPY1CHB+i212+av35bkPXXj56Y= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw= +github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/buger/goterm v1.0.1 h1:kSgw3jcjYUzC0Uh/eG8ULjccuz353solup27lUH8Zug= +github.com/buger/goterm v1.0.1/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= +github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI= +github.com/caddyserver/certmagic v0.15.2 h1:OMTakTsLM1ZfzMDjwvYprfUgFzpVPh3u87oxMPwmeBc= +github.com/caddyserver/certmagic v0.15.2/go.mod h1:qhkAOthf72ufAcp3Y5jF2RaGE96oip3UbEQRIzwe3/8= +github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/emicklei/proto v1.9.1 h1:MUgjFo5xlMwYv72TnF5xmmdKZ04u+dVbv6wdARv16D8= +github.com/emicklei/proto v1.9.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= +github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= +github.com/go-restit/lzjson v0.0.0-20161206095556-efe3c53acc68/go.mod h1:7vXSKQt83WmbPeyVjCfNT9YDJ5BUFmcwFsEjI9SCvYM= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= +github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= +github.com/goccy/go-json v0.9.1 h1:xurvfvj3gq6SWUkkZ0opoUDTms7jif41uZ9z9s+hVlY= +github.com/goccy/go-json v0.9.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofiber/fiber/v2 v2.24.0 h1:18rpLoQMJBVlLtX/PwgHj3hIxPSeWfN1YeDJ2lEnzjU= +github.com/gofiber/fiber/v2 v2.24.0/go.mod h1:MR1usVH3JHYRyQwMe2eZXRSZHRX38fkV+A7CPB+DlDQ= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0 h1:+eIkrewn5q6b30y+g/BJINVVdi2xH7je5MPJ3ZPK3JA= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= +github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.14.1 h1:hLQYb23E8/fO+1u53d02A97a8UnsddcvYzq4ERRU4ds= +github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= +github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mholt/acmez v1.0.1 h1:J7uquHOKEmo71UDnVApy1sSLA0oF/r+NtVrNzMKKA9I= +github.com/mholt/acmez v1.0.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk= +github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= +github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= +github.com/nats-io/jwt/v2 v2.1.0 h1:1UbfD5g1xTdWmSeRV8bh/7u+utTiBsRtWhLl1PixZp4= +github.com/nats-io/jwt/v2 v2.1.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/nats-server/v2 v2.6.1 h1:cJy+ia7/4EaJL+ZYDmIy2rD1mDWTfckhtPBU0GYo8xM= +github.com/nats-io/nats-server/v2 v2.6.1/go.mod h1:Az91TbZiV7K4a6k/4v6YYdOKEoxCXj+iqhHVf/MlrKo= +github.com/nats-io/nats.go v1.12.3/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.13.0 h1:LvYqRB5epIzZWQp6lmeltOOZNLqCvm4b+qfvzZO03HE= +github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/newrelic/go-agent/v3 v3.15.2 h1:NEpksu2AhuZncbwkDqUg2IvUJst3JQ/TemYfK4WdS/Y= +github.com/newrelic/go-agent/v3 v3.15.2/go.mod h1:1A1dssWBwzB7UemzRU6ZVaGDsI+cEn5/bNxI0wiYlIc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rabbitmq/amqp091-go v1.2.0 h1:1pHBxAsQh54R9eX/xo679fUEAfv3loMqi0pvRFOj2nk= +github.com/rabbitmq/amqp091-go v1.2.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/roadrunner-server/api/v2 v2.0.0-rc.1 h1:MpQimB7BumOLhyIxSyKdYQqaona7XvE2kVXTtVQgkl4= +github.com/roadrunner-server/api/v2 v2.0.0-rc.1/go.mod h1:dLgRibV6GMnrxOfmaZONAXoSp4Ej4L1S4g/UxazoxXA= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60= +github.com/spf13/afero v1.8.0/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= +github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/spiral/endure v1.1.0 h1:v9CzvnQvvLyR0aiZpA04ZTIq444x0FR2PTCJAPNEO6g= +github.com/spiral/endure v1.1.0/go.mod h1:M2AHi+x+YdQ4gW1rV2aDWGkT6hxJwVWpK4qRiax8Zzw= github.com/spiral/errors v1.0.12 h1:38Waf8ZL/Xvxg4HTYGmrUbvi7TCHivmuatNQZlBhQ8s= github.com/spiral/errors v1.0.12/go.mod h1:j5UReqxZxfkwXkI9mFY87VhEXcXmSg7kAk5Sswy1eEA= +github.com/spiral/goridge/v3 v3.2.4/go.mod h1:a6qAtZy+FBaPj/76GweHj6SkgIr+oRVgW5p4e5vLZF4= +github.com/spiral/goridge/v3 v3.2.6/go.mod h1:a6qAtZy+FBaPj/76GweHj6SkgIr+oRVgW5p4e5vLZF4= github.com/spiral/goridge/v3 v3.2.7 h1:tiV1d025QwEAIr8WRo7kUQW9PhWGa405QpC3HrEjCPY= github.com/spiral/goridge/v3 v3.2.7/go.mod h1:a6qAtZy+FBaPj/76GweHj6SkgIr+oRVgW5p4e5vLZF4= +github.com/spiral/roadrunner-plugins/v2 v2.7.0/go.mod h1:57IrBZzvi6LyuY9N+QhNh5rDrbDTxbmObupuE7HtC50= +github.com/spiral/roadrunner-plugins/v2 v2.7.1 h1:eMLJDuPam+I5DIPGKuwk82VzZo/kWzfdVhxl3vlSOQM= +github.com/spiral/roadrunner-plugins/v2 v2.7.1/go.mod h1:NuHOUIr0MmAko5YWsBHorD0Bvyx09PBX+Tzf4v5u1B0= +github.com/spiral/roadrunner/v2 v2.7.0-rc.2/go.mod h1:S/ash+OWnvvBR79EV+3YDk8uACC4p3Fi/MSDFqkA3Jo= +github.com/spiral/roadrunner/v2 v2.7.0/go.mod h1:MJVb9hFHZfVigPbATmcxgmP3WQG0QvW4bUR431Vw0js= +github.com/spiral/roadrunner/v2 v2.7.1 h1:D/i+lzZfddSwgvcAXG/2MCzBPTEB4KsPWjdGx4KsP8E= +github.com/spiral/roadrunner/v2 v2.7.1/go.mod h1:MJOw6nV5j0nCDn6qNogiyU4+WjTZJcc6EriA2f/pn78= +github.com/spiral/sdk-go v1.11.2/go.mod h1:iRDEyfVAr6PgFGaOv1+o5OVG9kmgTgspBwmO0GOY1p8= +github.com/spiral/sdk-go v1.12.0 h1:PSKXMVLwifyWhuveSdKlBJm5Grcs/0bRfp1XqXRO+QI= +github.com/spiral/sdk-go v1.12.0/go.mod h1:Y3IKVPm7/RAca9DDuTmFFo+f3Sj+QAm/MHo08Kb3oLY= +github.com/spiral/sdk-go/contrib/tally v0.0.0-20211210212330-614642034d01 h1:qQfQEQ3z6fG+FWxqyzUSiuUZpbWbze6hb5UthrT8i00= +github.com/spiral/sdk-go/contrib/tally v0.0.0-20211210212330-614642034d01/go.mod h1:fHu9AnyudU85kM53B1XeXb4RZq8G0OYgXc7g2rCMEbQ= github.com/spiral/tcplisten v1.0.0 h1:dII3R20Xslll6Uk60ac1JCn9zQwfwbt88CLrs3OryZg= github.com/spiral/tcplisten v1.0.0/go.mod h1:+anIsZh2ZYw2EogG2pO1yEZKcGN7lEf41hUQilctYJo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= +github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/temporalio/roadrunner-temporal v1.1.0 h1:oi1MgJxRJGmCK569EpQ2+RRYVTaOOoPP3LGoSbc2SyU= +github.com/temporalio/roadrunner-temporal v1.1.0/go.mod h1:sLAuIKRiQXhtx2iS2I/GExnz3q9NUIy6Byn2fVOR8Fg= github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= +github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/uber-go/tally/v4 v4.1.1 h1:jhy6WOZp4nHyCqeV43x3Wz370LXUGBhgW2JmzOIHCWI= +github.com/uber-go/tally/v4 v4.1.1/go.mod h1:aXeSTDMl4tNosyf6rdU8jlgScHyjEGGtfJ/uwCIf/vM= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/fasthttp v1.32.0 h1:keswgWzyKyNIIjz2a7JmCYHOOIkRp6HMx9oTV6QrZWY= +github.com/valyala/fasthttp v1.32.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vbauerster/mpb/v5 v5.4.0 h1:n8JPunifvQvh6P1D1HAl2Ur9YcmKT1tpoUuiea5mlmg= +github.com/vbauerster/mpb/v5 v5.4.0/go.mod h1:fi4wVo7BVQ22QcvFObm+VwliQXlV1eBT8JDaKXR4JGI= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/yookoala/gofast v0.6.0/go.mod h1:OJU201Q6HCaE1cASckaTbMm3KB6e0cZxK0mgqfwOKvQ= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v1.2.0/go.mod h1:aT17Fk0Z1Nor9e0uisf98LrntPGMnk4frBO9+dkf69I= +go.opentelemetry.io/otel/sdk v1.2.0/go.mod h1:jNN8QtpvbsKhgaC6V5lHiejMoKD+V8uadoSafgHPx1U= +go.opentelemetry.io/otel/trace v1.2.0/go.mod h1:N5FLswTubnxKxOJHM7XZC074qpeEdLy3CgAVsdMucK0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.temporal.io/api v1.6.0/go.mod h1:YLj7h12DVi4m1ox3BgdGyD6OKO1yDn/cW4pO/SLrkcU= +go.temporal.io/api v1.7.0 h1:fMaxrk8u12zPPOKgN6HCHyJjQQX6HcCxtMQTjck1rGE= +go.temporal.io/api v1.7.0/go.mod h1:Bjxr81kDTMY0IYxbosWleAVOFE+Pnp4SRk87oWchYv8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc= go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI= +golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220111093109-d55c255bac03 h1:0FB83qp0AzVJm+0wcIlauAjJ+tNdh7jLuacRYCIVv7s= +golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211209171907-798191bca915/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200908211811-12e1bf57a112/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211101144312-62acf1d99145/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2 h1:z+R4M/SuyaRsj1zu3WC+nIQyfSrSIpuDcY01/R3uCtg= +google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/cli/reset/command.go b/internal/cli/reset/command.go new file mode 100644 index 00000000..d6cf7087 --- /dev/null +++ b/internal/cli/reset/command.go @@ -0,0 +1,104 @@ +package reset + +import ( + "fmt" + "sync" + + internalRpc "github.com/spiral/roadrunner-binary/v2/internal/rpc" + + "github.com/fatih/color" + "github.com/mattn/go-runewidth" + "github.com/spf13/cobra" + "github.com/spiral/errors" + "github.com/spiral/roadrunner-plugins/v2/config" + "github.com/vbauerster/mpb/v5" + "github.com/vbauerster/mpb/v5/decor" +) + +var spinnerStyle = []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"} //nolint:gochecknoglobals + +// NewCommand creates `reset` command. +func NewCommand(cfgPlugin *config.Plugin) *cobra.Command { //nolint:funlen + return &cobra.Command{ + Use: "reset", + Short: "Reset workers of all or specific RoadRunner service", + RunE: func(_ *cobra.Command, args []string) error { + const ( + op = errors.Op("reset_handler") + resetterList = "resetter.List" + resetterReset = "resetter.Reset" + ) + + client, err := internalRpc.NewClient(cfgPlugin) + if err != nil { + return err + } + + defer func() { _ = client.Close() }() + + services := args // by default we expect services list from user + if len(services) == 0 { // but if nothing was passed - request all services list + if err = client.Call(resetterList, true, &services); err != nil { + return err + } + } + + var wg sync.WaitGroup + wg.Add(len(services)) + + pr := mpb.New(mpb.WithWaitGroup(&wg), mpb.WithWidth(6)) //nolint:gomnd + + for _, service := range services { + var ( + bar *mpb.Bar + name = runewidth.FillRight(fmt.Sprintf("Resetting plugin: [%s]", color.HiYellowString(service)), 27) + result = make(chan interface{}) + ) + + bar = pr.AddSpinner( + 1, + mpb.SpinnerOnMiddle, + mpb.SpinnerStyle(spinnerStyle), + mpb.PrependDecorators(decor.Name(name)), + mpb.AppendDecorators(onComplete(result)), + ) + + // simulating some work + go func(service string, result chan interface{}) { + defer wg.Done() + defer bar.Increment() + + var done bool + <-client.Go(resetterReset, service, &done, nil).Done + + if err != nil { + result <- errors.E(op, err) + + return + } + + result <- nil + }(service, result) + } + + pr.Wait() + + return nil + }, + } +} + +func onComplete(result chan interface{}) decor.Decorator { + return decor.Any(func(s decor.Statistics) string { + select { + case r := <-result: + if err, ok := r.(error); ok { + return color.HiRedString(err.Error()) + } + + return color.HiGreenString("done") + default: + return "" + } + }) +} diff --git a/internal/cli/reset/command_test.go b/internal/cli/reset/command_test.go new file mode 100644 index 00000000..00cd046e --- /dev/null +++ b/internal/cli/reset/command_test.go @@ -0,0 +1,21 @@ +package reset_test + +import ( + "testing" + + "github.com/spiral/roadrunner-binary/v2/internal/cli/reset" + + "github.com/spiral/roadrunner-plugins/v2/config" + "github.com/stretchr/testify/assert" +) + +func TestCommandProperties(t *testing.T) { + cmd := reset.NewCommand(&config.Plugin{}) + + assert.Equal(t, "reset", cmd.Use) + assert.NotNil(t, cmd.RunE) +} + +func TestExecution(t *testing.T) { + t.Skip("Command execution is not implemented yet") +} diff --git a/internal/cli/root.go b/internal/cli/root.go new file mode 100644 index 00000000..8572bdc6 --- /dev/null +++ b/internal/cli/root.go @@ -0,0 +1,98 @@ +package cli + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + "github.com/spiral/roadrunner-binary/v2/internal/cli/reset" + "github.com/spiral/roadrunner-binary/v2/internal/cli/serve" + "github.com/spiral/roadrunner-binary/v2/internal/cli/workers" + dbg "github.com/spiral/roadrunner-binary/v2/internal/debug" + "github.com/spiral/roadrunner-binary/v2/internal/meta" + + "github.com/joho/godotenv" + "github.com/spf13/cobra" + "github.com/spiral/roadrunner-plugins/v2/config" +) + +// NewCommand creates root command. +func NewCommand(cmdName string) *cobra.Command { //nolint:funlen + const envDotenv = "DOTENV_PATH" // env var name: path to the .env file + + var ( // flag values + cfgFile string // path to the .rr.yaml + workDir string // working directory + dotenv string // path to the .env file + debug bool // debug mode + override []string // override config values + ) + + var configPlugin = &config.Plugin{} // will be overwritten on pre-run action + + cmd := &cobra.Command{ + Use: cmdName, + Short: "High-performance PHP application server, load-balancer and process manager", + SilenceErrors: true, + SilenceUsage: true, + Version: fmt.Sprintf("%s (build time: %s, %s)", meta.Version(), meta.BuildTime(), runtime.Version()), + PersistentPreRunE: func(*cobra.Command, []string) error { + if cfgFile != "" { + if absPath, err := filepath.Abs(cfgFile); err == nil { + cfgFile = absPath // switch config path to the absolute + + // force working absPath related to config file + if err = os.Chdir(filepath.Dir(absPath)); err != nil { + return err + } + } + } + + if workDir != "" { + if err := os.Chdir(workDir); err != nil { + return err + } + } + + if v, ok := os.LookupEnv(envDotenv); ok { // read path to the dotenv file from environment variable + dotenv = v + } + + if dotenv != "" { + _ = godotenv.Load(dotenv) // error ignored because dotenv is optional feature + } + + cfg := &config.Plugin{Path: cfgFile, Prefix: "rr", Flags: override} + if err := cfg.Init(); err != nil { + return err + } + + if debug { + srv := dbg.NewServer() + go func() { _ = srv.Start(":6061") }() // TODO implement graceful server stopping + } + + // overwrite + *configPlugin = *cfg + + return nil + }, + } + + f := cmd.PersistentFlags() + + f.StringVarP(&cfgFile, "config", "c", ".rr.yaml", "config file") + f.StringVarP(&workDir, "WorkDir", "w", "", "working directory") // TODO change to `workDir`? + f.StringVarP(&dotenv, "dotenv", "", "", fmt.Sprintf("dotenv file [$%s]", envDotenv)) + f.BoolVarP(&debug, "debug", "d", false, "debug mode") + f.StringArrayVarP(&override, "override", "o", nil, "override config value (dot.notation=value)") + + cmd.AddCommand( + workers.NewCommand(configPlugin), + reset.NewCommand(configPlugin), + serve.NewCommand(configPlugin), + ) + + return cmd +} diff --git a/internal/cli/root_test.go b/internal/cli/root_test.go new file mode 100644 index 00000000..59af9294 --- /dev/null +++ b/internal/cli/root_test.go @@ -0,0 +1,85 @@ +package cli_test + +import ( + "testing" + + "github.com/spiral/roadrunner-binary/v2/internal/cli" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +func TestCommandSubcommands(t *testing.T) { + cmd := cli.NewCommand("unit test") + + cases := []struct { + giveName string + }{ + {giveName: "workers"}, + {giveName: "reset"}, + {giveName: "serve"}, + } + + // get all existing subcommands and put into the map + subcommands := make(map[string]*cobra.Command) + for _, sub := range cmd.Commands() { + subcommands[sub.Name()] = sub + } + + for _, tt := range cases { + tt := tt + t.Run(tt.giveName, func(t *testing.T) { + if _, exists := subcommands[tt.giveName]; !exists { + assert.Failf(t, "command not found", "command [%s] was not found", tt.giveName) + } + }) + } +} + +func TestCommandFlags(t *testing.T) { + cmd := cli.NewCommand("unit test") + + cases := []struct { + giveName string + wantShorthand string + wantDefault string + }{ + {giveName: "config", wantShorthand: "c", wantDefault: ".rr.yaml"}, + {giveName: "WorkDir", wantShorthand: "w", wantDefault: ""}, + {giveName: "dotenv", wantShorthand: "", wantDefault: ""}, + {giveName: "debug", wantShorthand: "d", wantDefault: "false"}, + {giveName: "override", wantShorthand: "o", wantDefault: "[]"}, + } + + for _, tt := range cases { + tt := tt + t.Run(tt.giveName, func(t *testing.T) { + flag := cmd.Flag(tt.giveName) + + if flag == nil { + assert.Failf(t, "flag not found", "flag [%s] was not found", tt.giveName) + + return + } + + assert.Equal(t, tt.wantShorthand, flag.Shorthand) + assert.Equal(t, tt.wantDefault, flag.DefValue) + }) + } +} + +func TestCommandSimpleExecuting(t *testing.T) { + cmd := cli.NewCommand("unit test") + cmd.SetArgs([]string{"-c", "./../../.rr.yaml"}) + + var executed bool + + if cmd.Run == nil { // override "Run" property for test (if it was not set) + cmd.Run = func(cmd *cobra.Command, args []string) { + executed = true + } + } + + assert.NoError(t, cmd.Execute()) + assert.True(t, executed) +} diff --git a/internal/cli/serve/command.go b/internal/cli/serve/command.go new file mode 100644 index 00000000..6679d795 --- /dev/null +++ b/internal/cli/serve/command.go @@ -0,0 +1,104 @@ +package serve + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/spiral/roadrunner-binary/v2/internal/container" + "github.com/spiral/roadrunner-binary/v2/internal/meta" + + "github.com/spf13/cobra" + "github.com/spiral/errors" + configImpl "github.com/spiral/roadrunner-plugins/v2/config" +) + +// NewCommand creates `serve` command. +func NewCommand(cfgPlugin *configImpl.Plugin) *cobra.Command { //nolint:funlen + return &cobra.Command{ + Use: "serve", + Short: "Start RoadRunner server", + RunE: func(*cobra.Command, []string) error { + const op = errors.Op("handle_serve_command") + + // create endure container config + containerCfg, err := container.NewConfig(cfgPlugin) + if err != nil { + return errors.E(op, err) + } + + // set the grace period which would be same for all the plugins + cfgPlugin.Timeout = containerCfg.GracePeriod + cfgPlugin.Version = meta.Version() + + // create endure container + endureContainer, err := container.NewContainer(*containerCfg) + if err != nil { + return errors.E(op, err) + } + + // register config plugin + if err = endureContainer.Register(cfgPlugin); err != nil { + return errors.E(op, err) + } + + // register another container plugins + for i, plugins := 0, container.Plugins(); i < len(plugins); i++ { + if err = endureContainer.Register(plugins[i]); err != nil { + return errors.E(op, err) + } + } + + // init container and all services + if err = endureContainer.Init(); err != nil { + return errors.E(op, err) + } + + // start serving the graph + errCh, err := endureContainer.Serve() + if err != nil { + return errors.E(op, err) + } + + oss, stop := make(chan os.Signal, 2), make(chan struct{}, 1) //nolint:gomnd + signal.Notify(oss, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + + go func() { + // first catch - stop the container + <-oss + // send signal to stop execution + stop <- struct{}{} + + // after first hit we are waiting for the second + // second catch - exit from the process + <-oss + fmt.Println("exit forced") + os.Exit(1) + }() + + fmt.Printf("[INFO] RoadRunner server started; version: %s, buildtime: %s\n", meta.Version(), meta.BuildTime()) + + for { + select { + case e := <-errCh: + fmt.Printf("error occurred: %v, plugin: %s\n", e.Error, e.VertexID) + + // return error, container already stopped internally + if !containerCfg.RetryOnFail { + return errors.E(op, e.Error) + } + + case <-stop: // stop the container after first signal + fmt.Printf("stop signal received, grace timeout is: %d seconds\n", uint64(containerCfg.GracePeriod.Seconds())) + + if err = endureContainer.Stop(); err != nil { + fmt.Printf("error occurred during the stopping container: %v\n", err) + } + + return nil + } + } + }, + } +} diff --git a/internal/cli/serve/command_test.go b/internal/cli/serve/command_test.go new file mode 100644 index 00000000..0e61ce83 --- /dev/null +++ b/internal/cli/serve/command_test.go @@ -0,0 +1,21 @@ +package serve_test + +import ( + "testing" + + "github.com/spiral/roadrunner-binary/v2/internal/cli/serve" + + "github.com/spiral/roadrunner-plugins/v2/config" + "github.com/stretchr/testify/assert" +) + +func TestCommandProperties(t *testing.T) { + cmd := serve.NewCommand(&config.Plugin{}) + + assert.Equal(t, "serve", cmd.Use) + assert.NotNil(t, cmd.RunE) +} + +func TestExecution(t *testing.T) { + t.Skip("Command execution is not implemented yet") +} diff --git a/internal/cli/workers/command.go b/internal/cli/workers/command.go new file mode 100644 index 00000000..283887e4 --- /dev/null +++ b/internal/cli/workers/command.go @@ -0,0 +1,143 @@ +package workers + +import ( + "fmt" + "net/rpc" + "os" + "os/signal" + "syscall" + "time" + + "github.com/roadrunner-server/api/v2/plugins/jobs" + internalRpc "github.com/spiral/roadrunner-binary/v2/internal/rpc" + + tm "github.com/buger/goterm" + "github.com/fatih/color" + "github.com/spf13/cobra" + "github.com/spiral/errors" + "github.com/spiral/roadrunner-plugins/v2/config" + "github.com/spiral/roadrunner-plugins/v2/informer" +) + +// NewCommand creates `workers` command. +func NewCommand(cfgPlugin *config.Plugin) *cobra.Command { //nolint:funlen + var ( + // interactive workers updates + interactive bool + ) + + cmd := &cobra.Command{ + Use: "workers", + Short: "Show information about active RoadRunner workers", + RunE: func(_ *cobra.Command, args []string) error { + const ( + op = errors.Op("handle_workers_command") + informerList = "informer.List" + ) + + client, err := internalRpc.NewClient(cfgPlugin) + if err != nil { + return err + } + + defer func() { _ = client.Close() }() + + plugins := args // by default we expect plugins list from user + if len(plugins) == 0 { // but if nothing was passed - request all informers list + if err = client.Call(informerList, true, &plugins); err != nil { + return err + } + } + + if !interactive { + return showWorkers(plugins, client) + } + + oss := make(chan os.Signal, 1) + signal.Notify(oss, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + + tm.Clear() + + tt := time.NewTicker(time.Second) + defer tt.Stop() + + for { + select { + case <-oss: + return nil + + case <-tt.C: + tm.MoveCursor(1, 1) + tm.Flush() + + if err = showWorkers(plugins, client); err != nil { + return errors.E(op, err) + } + } + } + }, + } + + cmd.Flags().BoolVarP( + &interactive, + "interactive", + "i", + false, + "render interactive workers table", + ) + + return cmd +} + +func showWorkers(plugins []string, client *rpc.Client) error { + const ( + op = errors.Op("show_workers") + informerWorkers = "informer.Workers" + informerJobs = "informer.Jobs" + // this is only one exception to Render the workers, service plugin has the same workers as other plugins, + // but they are RAW processes and needs to be handled in a different way. We don't need a special RPC call, but + // need a special render method. + servicePluginName = "service" + ) + + for _, plugin := range plugins { + list := &informer.WorkerList{} + + if err := client.Call(informerWorkers, plugin, &list); err != nil { + return errors.E(op, err) + } + + if len(list.Workers) == 0 { + continue + } + + if plugin == servicePluginName { + fmt.Printf("Workers of [%s]:\n", color.HiYellowString(plugin)) + ServiceWorkerTable(os.Stdout, list.Workers).Render() + + continue + } + + fmt.Printf("Workers of [%s]:\n", color.HiYellowString(plugin)) + + WorkerTable(os.Stdout, list.Workers).Render() + } + + for _, plugin := range plugins { + var jst []*jobs.State + + if err := client.Call(informerJobs, plugin, &jst); err != nil { + return errors.E(op, err) + } + + // eq to nil + if len(jst) == 0 { + continue + } + + fmt.Printf("Jobs of [%s]:\n", color.HiYellowString(plugin)) + JobsTable(os.Stdout, jst).Render() + } + + return nil +} diff --git a/internal/cli/workers/command_test.go b/internal/cli/workers/command_test.go new file mode 100644 index 00000000..e593686d --- /dev/null +++ b/internal/cli/workers/command_test.go @@ -0,0 +1,49 @@ +package workers_test + +import ( + "testing" + + "github.com/spiral/roadrunner-binary/v2/internal/cli/workers" + + "github.com/spiral/roadrunner-plugins/v2/config" + "github.com/stretchr/testify/assert" +) + +func TestCommandProperties(t *testing.T) { + cmd := workers.NewCommand(&config.Plugin{}) + + assert.Equal(t, "workers", cmd.Use) + assert.NotNil(t, cmd.RunE) +} + +func TestCommandFlags(t *testing.T) { + cmd := workers.NewCommand(&config.Plugin{}) + + cases := []struct { + giveName string + wantShorthand string + wantDefault string + }{ + {giveName: "interactive", wantShorthand: "i", wantDefault: "false"}, + } + + for _, tt := range cases { + tt := tt + t.Run(tt.giveName, func(t *testing.T) { + flag := cmd.Flag(tt.giveName) + + if flag == nil { + assert.Failf(t, "flag not found", "flag [%s] was not found", tt.giveName) + + return + } + + assert.Equal(t, tt.wantShorthand, flag.Shorthand) + assert.Equal(t, tt.wantDefault, flag.DefValue) + }) + } +} + +func TestExecution(t *testing.T) { + t.Skip("Command execution is not implemented yet") +} diff --git a/internal/cli/workers/render.go b/internal/cli/workers/render.go new file mode 100644 index 00000000..0bdf09b6 --- /dev/null +++ b/internal/cli/workers/render.go @@ -0,0 +1,135 @@ +package workers + +import ( + "io" + "strconv" + "time" + + "github.com/dustin/go-humanize" + "github.com/fatih/color" + "github.com/olekukonko/tablewriter" + "github.com/roadrunner-server/api/v2/plugins/jobs" + "github.com/spiral/roadrunner/v2/state/process" +) + +const ( + Ready string = "READY" + Paused string = "PAUSED/STOPPED" +) + +// WorkerTable renders table with information about rr server workers. +func WorkerTable(writer io.Writer, workers []*process.State) *tablewriter.Table { + tw := tablewriter.NewWriter(writer) + tw.SetHeader([]string{"PID", "Status", "Execs", "Memory", "CPU%", "Created"}) + tw.SetColMinWidth(0, 7) + tw.SetColMinWidth(1, 9) + tw.SetColMinWidth(2, 7) + tw.SetColMinWidth(3, 7) + tw.SetColMinWidth(4, 7) + tw.SetColMinWidth(5, 18) + + for i := 0; i < len(workers); i++ { + tw.Append([]string{ + strconv.Itoa(workers[i].Pid), + renderStatus(workers[i].Status), + renderJobs(workers[i].NumJobs), + humanize.Bytes(workers[i].MemoryUsage), + renderCPU(workers[i].CPUPercent), + renderAlive(time.Unix(0, workers[i].Created)), + }) + } + + return tw +} + +// ServiceWorkerTable renders table with information about rr server workers. +func ServiceWorkerTable(writer io.Writer, workers []*process.State) *tablewriter.Table { + tw := tablewriter.NewWriter(writer) + tw.SetAutoWrapText(false) + tw.SetHeader([]string{"PID", "Memory", "CPU%", "Command"}) + tw.SetColMinWidth(0, 7) + tw.SetColMinWidth(1, 7) + tw.SetColMinWidth(2, 7) + tw.SetColMinWidth(3, 18) + tw.SetAlignment(tablewriter.ALIGN_LEFT) + + for i := 0; i < len(workers); i++ { + tw.Append([]string{ + strconv.Itoa(workers[i].Pid), + humanize.Bytes(workers[i].MemoryUsage), + renderCPU(workers[i].CPUPercent), + workers[i].Command, + }) + } + + return tw +} + +// JobsTable renders table with information about rr server jobs. +func JobsTable(writer io.Writer, jobs []*jobs.State) *tablewriter.Table { + tw := tablewriter.NewWriter(writer) + tw.SetAutoWrapText(false) + tw.SetHeader([]string{"Status", "Pipeline", "Driver", "Queue", "Active", "Delayed", "Reserved"}) + tw.SetColWidth(10) + tw.SetColWidth(10) + tw.SetColWidth(7) + tw.SetColWidth(15) + tw.SetColWidth(10) + tw.SetColWidth(10) + tw.SetColWidth(10) + tw.SetAlignment(tablewriter.ALIGN_LEFT) + + for i := 0; i < len(jobs); i++ { + tw.Append([]string{ + renderReady(jobs[i].Ready), + jobs[i].Pipeline, + jobs[i].Driver, + jobs[i].Queue, + strconv.Itoa(int(jobs[i].Active)), + strconv.Itoa(int(jobs[i].Delayed)), + strconv.Itoa(int(jobs[i].Reserved)), + }) + } + + return tw +} + +func renderReady(ready bool) string { + if ready { + return Ready + } + + return Paused +} + +//go:inline +func renderCPU(cpu float64) string { + return strconv.FormatFloat(cpu, 'f', 2, 64) +} + +func renderStatus(status string) string { + switch status { + case "inactive": + return color.YellowString("inactive") + case "ready": + return color.CyanString("ready") + case "working": + return color.GreenString("working") + case "invalid": + return color.YellowString("invalid") + case "stopped": + return color.RedString("stopped") + case "errored": + return color.RedString("errored") + default: + return status + } +} + +func renderJobs(number uint64) string { + return humanize.Comma(int64(number)) +} + +func renderAlive(t time.Time) string { + return humanize.RelTime(t, time.Now(), "ago", "") +} diff --git a/internal/container/config.go b/internal/container/config.go new file mode 100644 index 00000000..54e2bb5b --- /dev/null +++ b/internal/container/config.go @@ -0,0 +1,83 @@ +package container + +import ( + "fmt" + "time" + + endure "github.com/spiral/endure/pkg/container" + "github.com/spiral/roadrunner-plugins/v2/config" +) + +type Config struct { + GracePeriod time.Duration + PrintGraph bool + RetryOnFail bool // TODO check for races, disabled at this moment + LogLevel endure.Level +} + +const ( + endureKey = "endure" + defaultGracePeriod = time.Second * 30 +) + +// NewConfig creates endure container configuration. +func NewConfig(cfgPlugin *config.Plugin) (*Config, error) { + if !cfgPlugin.Has(endureKey) { + return &Config{ // return config with defaults + GracePeriod: defaultGracePeriod, + PrintGraph: false, + RetryOnFail: false, + LogLevel: endure.ErrorLevel, + }, nil + } + + rrCfgEndure := struct { + GracePeriod time.Duration `mapstructure:"grace_period"` + PrintGraph bool `mapstructure:"print_graph"` + RetryOnFail bool `mapstructure:"retry_on_fail"` + LogLevel string `mapstructure:"log_level"` + }{} + + if err := cfgPlugin.UnmarshalKey(endureKey, &rrCfgEndure); err != nil { + return nil, err + } + + if rrCfgEndure.GracePeriod == 0 { + rrCfgEndure.GracePeriod = defaultGracePeriod + } + + if rrCfgEndure.LogLevel == "" { + rrCfgEndure.LogLevel = "error" + } + + logLevel, err := parseLogLevel(rrCfgEndure.LogLevel) + if err != nil { + return nil, err + } + + return &Config{ + GracePeriod: rrCfgEndure.GracePeriod, + PrintGraph: rrCfgEndure.PrintGraph, + RetryOnFail: rrCfgEndure.RetryOnFail, + LogLevel: logLevel, + }, nil +} + +func parseLogLevel(s string) (endure.Level, error) { + switch s { + case "debug": + return endure.DebugLevel, nil + case "info": + return endure.InfoLevel, nil + case "warn", "warning": + return endure.WarnLevel, nil + case "error": + return endure.ErrorLevel, nil + case "panic": + return endure.PanicLevel, nil + case "fatal": + return endure.FatalLevel, nil + } + + return endure.DebugLevel, fmt.Errorf(`unknown log level "%s" (allowed: debug, info, warn, error, panic, fatal)`, s) +} diff --git a/internal/container/config_test.go b/internal/container/config_test.go new file mode 100644 index 00000000..9919def4 --- /dev/null +++ b/internal/container/config_test.go @@ -0,0 +1,82 @@ +package container_test + +import ( + "testing" + "time" + + "github.com/spiral/roadrunner-binary/v2/internal/container" + + endure "github.com/spiral/endure/pkg/container" + "github.com/spiral/roadrunner-plugins/v2/config" + "github.com/stretchr/testify/assert" +) + +func TestNewConfig_SuccessfulReading(t *testing.T) { + cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte(` +endure: + grace_period: 10s + print_graph: true + retry_on_fail: true + log_level: warn +`)} + assert.NoError(t, cfgPlugin.Init()) + + c, err := container.NewConfig(cfgPlugin) + assert.NoError(t, err) + assert.NotNil(t, c) + + assert.Equal(t, time.Second*10, c.GracePeriod) + assert.True(t, c.PrintGraph) + assert.True(t, c.RetryOnFail) + assert.Equal(t, endure.WarnLevel, c.LogLevel) +} + +func TestNewConfig_WithoutEndureKey(t *testing.T) { + cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte{}} + assert.NoError(t, cfgPlugin.Init()) + + c, err := container.NewConfig(cfgPlugin) + assert.NoError(t, err) + assert.NotNil(t, c) + + assert.Equal(t, time.Second*30, c.GracePeriod) + assert.False(t, c.PrintGraph) + assert.False(t, c.RetryOnFail) + assert.Equal(t, endure.ErrorLevel, c.LogLevel) +} + +func TestNewConfig_LoggingLevels(t *testing.T) { + for _, tt := range []struct { + giveLevel string + wantLevel endure.Level + wantError bool + }{ + {giveLevel: "debug", wantLevel: endure.DebugLevel}, + {giveLevel: "info", wantLevel: endure.InfoLevel}, + {giveLevel: "warn", wantLevel: endure.WarnLevel}, + {giveLevel: "warning", wantLevel: endure.WarnLevel}, + {giveLevel: "error", wantLevel: endure.ErrorLevel}, + {giveLevel: "panic", wantLevel: endure.PanicLevel}, + {giveLevel: "fatal", wantLevel: endure.FatalLevel}, + + {giveLevel: "foobar", wantError: true}, + } { + tt := tt + t.Run(tt.giveLevel, func(t *testing.T) { + cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte("endure:\n log_level: " + tt.giveLevel)} + assert.NoError(t, cfgPlugin.Init()) + + c, err := container.NewConfig(cfgPlugin) + + if tt.wantError { + assert.Nil(t, c) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unknown log level") + } else { + assert.NoError(t, err) + assert.NotNil(t, c) + assert.Equal(t, tt.wantLevel, c.LogLevel) + } + }) + } +} diff --git a/internal/container/container.go b/internal/container/container.go new file mode 100644 index 00000000..aa767b2e --- /dev/null +++ b/internal/container/container.go @@ -0,0 +1,21 @@ +package container + +import ( + endure "github.com/spiral/endure/pkg/container" +) + +// NewContainer creates endure container with all required options (based on container Config). Logger is nil by +// default. +func NewContainer(cfg Config) (*endure.Endure, error) { + endureOptions := []endure.Options{ + endure.SetLogLevel(cfg.LogLevel), + endure.RetryOnFail(cfg.RetryOnFail), + endure.GracefulShutdownTimeout(cfg.GracePeriod), + } + + if cfg.PrintGraph { + endureOptions = append(endureOptions, endure.Visualize(endure.StdOut, "")) + } + + return endure.NewContainer(nil, endureOptions...) +} diff --git a/internal/container/container_test.go b/internal/container/container_test.go new file mode 100644 index 00000000..c6d613a0 --- /dev/null +++ b/internal/container/container_test.go @@ -0,0 +1,27 @@ +package container_test + +import ( + "testing" + "time" + + "github.com/spiral/roadrunner-binary/v2/internal/container" + + endure "github.com/spiral/endure/pkg/container" + "github.com/stretchr/testify/assert" +) + +func TestNewContainer(t *testing.T) { // there is no legal way to test container options + c, err := container.NewContainer(container.Config{}) + c2, err2 := container.NewContainer(container.Config{ + GracePeriod: time.Second, + PrintGraph: true, + RetryOnFail: true, + LogLevel: endure.WarnLevel, + }) + + assert.NoError(t, err) + assert.NotNil(t, c) + + assert.NoError(t, err2) + assert.NotNil(t, c2) +} diff --git a/internal/container/plugins.go b/internal/container/plugins.go new file mode 100644 index 00000000..6c962793 --- /dev/null +++ b/internal/container/plugins.go @@ -0,0 +1,104 @@ +package container + +import ( + "github.com/spiral/roadrunner-plugins/v2/amqp" + "github.com/spiral/roadrunner-plugins/v2/beanstalk" + "github.com/spiral/roadrunner-plugins/v2/boltdb" + "github.com/spiral/roadrunner-plugins/v2/broadcast" + "github.com/spiral/roadrunner-plugins/v2/fileserver" + grpcPlugin "github.com/spiral/roadrunner-plugins/v2/grpc" + httpPlugin "github.com/spiral/roadrunner-plugins/v2/http" + "github.com/spiral/roadrunner-plugins/v2/http/middleware/gzip" + "github.com/spiral/roadrunner-plugins/v2/http/middleware/headers" + newrelic "github.com/spiral/roadrunner-plugins/v2/http/middleware/new_relic" + "github.com/spiral/roadrunner-plugins/v2/http/middleware/prometheus" + "github.com/spiral/roadrunner-plugins/v2/http/middleware/static" + "github.com/spiral/roadrunner-plugins/v2/http/middleware/websockets" + "github.com/spiral/roadrunner-plugins/v2/informer" + "github.com/spiral/roadrunner-plugins/v2/jobs" + "github.com/spiral/roadrunner-plugins/v2/kv" + "github.com/spiral/roadrunner-plugins/v2/logger" + "github.com/spiral/roadrunner-plugins/v2/memcached" + "github.com/spiral/roadrunner-plugins/v2/memory" + "github.com/spiral/roadrunner-plugins/v2/metrics" + "github.com/spiral/roadrunner-plugins/v2/nats" + "github.com/spiral/roadrunner-plugins/v2/redis" + "github.com/spiral/roadrunner-plugins/v2/reload" + "github.com/spiral/roadrunner-plugins/v2/resetter" + rpcPlugin "github.com/spiral/roadrunner-plugins/v2/rpc" + "github.com/spiral/roadrunner-plugins/v2/server" + "github.com/spiral/roadrunner-plugins/v2/service" + "github.com/spiral/roadrunner-plugins/v2/sqs" + "github.com/spiral/roadrunner-plugins/v2/status" + "github.com/spiral/roadrunner-plugins/v2/tcp" + roadrunner_temporal "github.com/temporalio/roadrunner-temporal" +) + +// Plugins returns active plugins for the endure container. Feel free to add or remove any plugins. +func Plugins() []interface{} { //nolint:funlen + return []interface{}{ + // bundled + // informer plugin (./rr workers, ./rr workers -i) + &informer.Plugin{}, + // resetter plugin (./rr reset) + &resetter.Plugin{}, + + // logger plugin + &logger.ZapLogger{}, + // metrics plugin + &metrics.Plugin{}, + // reload plugin + &reload.Plugin{}, + // rpc plugin (workers, reset) + &rpcPlugin.Plugin{}, + // server plugin (NewWorker, NewWorkerPool) + &server.Plugin{}, + // service plugin + &service.Plugin{}, + + // ========= JOBS bundle + &jobs.Plugin{}, + &amqp.Plugin{}, + &sqs.Plugin{}, + &nats.Plugin{}, + &beanstalk.Plugin{}, + // ========= + + // http server plugin with middleware + &httpPlugin.Plugin{}, + &newrelic.Plugin{}, + &static.Plugin{}, + &headers.Plugin{}, + &status.Plugin{}, + &gzip.Plugin{}, + &prometheus.Plugin{}, + + &fileserver.Plugin{}, + // =================== + + &grpcPlugin.Plugin{}, + // kv + ws + jobs plugin + &memory.Plugin{}, + // KV + Jobs + &boltdb.Plugin{}, + + // broadcast via memory or redis + // used in conjunction with Websockets, memory and redis plugins + &broadcast.Plugin{}, + // ======== websockets broadcast bundle + &websockets.Plugin{}, + &redis.Plugin{}, + // ========= + + // ============== KV + &kv.Plugin{}, + &memcached.Plugin{}, + // ============== + + // raw TCP connections handling + &tcp.Plugin{}, + + // temporal plugins + &roadrunner_temporal.Plugin{}, + } +} diff --git a/internal/container/plugins_test.go b/internal/container/plugins_test.go new file mode 100644 index 00000000..da639f7d --- /dev/null +++ b/internal/container/plugins_test.go @@ -0,0 +1,20 @@ +package container_test + +import ( + "reflect" + "testing" + + "github.com/spiral/roadrunner-binary/v2/internal/container" +) + +func TestPlugins(t *testing.T) { + for _, p := range container.Plugins() { + if p == nil { + t.Error("plugin cannot be nil") + } + + if pk := reflect.TypeOf(p).Kind(); pk != reflect.Ptr && pk != reflect.Struct { + t.Errorf("plugin %v must be a structure or pointer to the structure", p) + } + } +} diff --git a/internal/debug/server.go b/internal/debug/server.go new file mode 100644 index 00000000..c07a4549 --- /dev/null +++ b/internal/debug/server.go @@ -0,0 +1,37 @@ +package debug + +import ( + "context" + "net/http" + "net/http/pprof" +) + +// Server is a HTTP server for debugging. +type Server struct { + srv *http.Server +} + +// NewServer creates new HTTP server for debugging. +func NewServer() Server { + mux := http.NewServeMux() + + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + return Server{srv: &http.Server{Handler: mux}} +} + +// Start debug server. +func (s *Server) Start(addr string) error { + s.srv.Addr = addr + + return s.srv.ListenAndServe() +} + +// Stop debug server. +func (s *Server) Stop(ctx context.Context) error { + return s.srv.Shutdown(ctx) +} diff --git a/internal/debug/server_test.go b/internal/debug/server_test.go new file mode 100644 index 00000000..d2e1f9f0 --- /dev/null +++ b/internal/debug/server_test.go @@ -0,0 +1,57 @@ +package debug_test + +import ( + "context" + "math/rand" + "net" + "net/http" + "strconv" + "testing" + "time" + + "github.com/spiral/roadrunner-binary/v2/internal/debug" + + "github.com/stretchr/testify/assert" +) + +func TestServer_StartingAndStopping(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + + var ( + s = debug.NewServer() + port = strconv.Itoa(rand.Intn(10000) + 10000) //nolint:gosec + ) + + go func() { assert.ErrorIs(t, s.Start(":"+port), http.ErrServerClosed) }() + + defer func() { assert.NoError(t, s.Stop(context.Background())) }() + + for i := 0; i < 100; i++ { // wait for server started state + if l, err := net.Dial("tcp", ":"+port); err != nil { + <-time.After(time.Millisecond) + } else { + _ = l.Close() + + break + } + } + + for _, uri := range []string{ // assert that pprof handlers exists + "http://127.0.0.1:" + port + "/debug/pprof/", + "http://127.0.0.1:" + port + "/debug/pprof/cmdline", + // "http://127.0.0.1:" + port + "/debug/pprof/profile", + "http://127.0.0.1:" + port + "/debug/pprof/symbol", + // "http://127.0.0.1:" + port + "/debug/pprof/trace", + } { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + + req, _ := http.NewRequestWithContext(ctx, http.MethodHead, uri, http.NoBody) + resp, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + + _ = resp.Body.Close() + + cancel() + } +} diff --git a/internal/meta/meta.go b/internal/meta/meta.go new file mode 100644 index 00000000..0c5a0556 --- /dev/null +++ b/internal/meta/meta.go @@ -0,0 +1,23 @@ +package meta + +import "strings" + +// next variables will be set during compilation (do NOT rename them). +var ( + version = "local" + buildTime = "development" //nolint:gochecknoglobals +) + +// Version returns version value (without `v` prefix). +func Version() string { + v := strings.TrimSpace(version) + + if len(v) > 1 && ((v[0] == 'v' || v[0] == 'V') && (v[1] >= '0' && v[1] <= '9')) { + return v[1:] + } + + return v +} + +// BuildTime returns application building time. +func BuildTime() string { return buildTime } diff --git a/internal/meta/meta_test.go b/internal/meta/meta_test.go new file mode 100644 index 00000000..32dee122 --- /dev/null +++ b/internal/meta/meta_test.go @@ -0,0 +1,49 @@ +package meta + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVersion(t *testing.T) { + for give, want := range map[string]string{ + // without changes + "vvv": "vvv", + "victory": "victory", + "voodoo": "voodoo", + "foo": "foo", + "0.0.0": "0.0.0", + "v": "v", + "V": "V", + + // "v" prefix removal + "v0.0.0": "0.0.0", + "V0.0.0": "0.0.0", + "v1": "1", + "V1": "1", + + // with spaces + " 0.0.0": "0.0.0", + "v0.0.0 ": "0.0.0", + " V0.0.0": "0.0.0", + "v1 ": "1", + " V1": "1", + "v ": "v", + } { + version = give + + assert.Equal(t, want, Version()) + } +} + +func TestBuildTime(t *testing.T) { + for give, want := range map[string]string{ + "development": "development", + "2021-03-26T13:50:31+0500": "2021-03-26T13:50:31+0500", + } { + buildTime = give + + assert.Equal(t, want, BuildTime()) + } +} diff --git a/internal/protocol.go b/internal/protocol.go deleted file mode 100755 index cefd685d..00000000 --- a/internal/protocol.go +++ /dev/null @@ -1,111 +0,0 @@ -package internal - -import ( - "os" - "sync" - - json "github.com/json-iterator/go" - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/pkg/frame" - "github.com/spiral/goridge/v3/pkg/relay" -) - -type StopCommand struct { - Stop bool `json:"stop"` -} - -type pidCommand struct { - Pid int `json:"pid"` -} - -var fPool = sync.Pool{New: func() interface{} { - return frame.NewFrame() -}} - -func getFrame() *frame.Frame { - return fPool.Get().(*frame.Frame) -} - -func putFrame(f *frame.Frame) { - f.Reset() - fPool.Put(f) -} - -func SendControl(rl relay.Relay, payload interface{}) error { - fr := getFrame() - defer putFrame(fr) - - fr.WriteVersion(fr.Header(), frame.VERSION_1) - fr.WriteFlags(fr.Header(), frame.CONTROL, frame.CODEC_JSON) - - if data, ok := payload.([]byte); ok { - // check if payload no more that 4Gb - if uint32(len(data)) > ^uint32(0) { - return errors.Str("payload is more that 4gb") - } - - fr.WritePayloadLen(fr.Header(), uint32(len(data))) - fr.WritePayload(data) - fr.WriteCRC(fr.Header()) - - err := rl.Send(fr) - if err != nil { - return err - } - return nil - } - - data, err := json.Marshal(payload) - if err != nil { - return errors.Errorf("invalid payload: %s", err) - } - - fr.WritePayloadLen(fr.Header(), uint32(len(data))) - fr.WritePayload(data) - fr.WriteCRC(fr.Header()) - - // we don't need a copy here, because frame copy the data before send - err = rl.Send(fr) - if err != nil { - return errors.E(errors.FileNotFound, err) - } - - return nil -} - -func Pid(rl relay.Relay) (int64, error) { - err := SendControl(rl, pidCommand{Pid: os.Getpid()}) - if err != nil { - return 0, err - } - - fr := getFrame() - defer putFrame(fr) - - err = rl.Receive(fr) - if err != nil { - return 0, err - } - - if fr == nil { - return 0, errors.Str("nil frame received") - } - - flags := fr.ReadFlags() - - if flags&frame.CONTROL == 0 { - return 0, errors.Str("unexpected response, header is missing, no CONTROL flag") - } - - link := &pidCommand{} - err = json.Unmarshal(fr.Payload(), link) - if err != nil { - return 0, err - } - - if link.Pid <= 0 { - return 0, errors.Str("pid should be greater than 0") - } - - return int64(link.Pid), nil -} diff --git a/internal/rpc/client.go b/internal/rpc/client.go new file mode 100644 index 00000000..f371a51c --- /dev/null +++ b/internal/rpc/client.go @@ -0,0 +1,33 @@ +// Package prc contains wrapper around RPC client ONLY for internal usage. +package rpc + +import ( + "net/rpc" + + "github.com/spiral/errors" + goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc" + "github.com/spiral/roadrunner-plugins/v2/config" + rpcPlugin "github.com/spiral/roadrunner-plugins/v2/rpc" +) + +// NewClient creates client ONLY for internal usage (communication between our application with RR side). +// Client will be connected to the RPC. +func NewClient(cfgPlugin *config.Plugin) (*rpc.Client, error) { + if !cfgPlugin.Has(rpcPlugin.PluginName) { + return nil, errors.E("rpc service disabled") + } + + rpcConfig := &rpcPlugin.Config{} + if err := cfgPlugin.UnmarshalKey(rpcPlugin.PluginName, rpcConfig); err != nil { + return nil, err + } + + rpcConfig.InitDefaults() + + conn, err := rpcConfig.Dialer() + if err != nil { + return nil, err + } + + return rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)), nil +} diff --git a/internal/rpc/client_test.go b/internal/rpc/client_test.go new file mode 100644 index 00000000..b39788a2 --- /dev/null +++ b/internal/rpc/client_test.go @@ -0,0 +1,60 @@ +package rpc_test + +import ( + "net" + "testing" + + "github.com/spiral/roadrunner-binary/v2/internal/rpc" + + "github.com/spiral/roadrunner-plugins/v2/config" + "github.com/stretchr/testify/assert" +) + +func TestNewClient_RpcServiceDisabled(t *testing.T) { + cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte{}} + assert.NoError(t, cfgPlugin.Init()) + + c, err := rpc.NewClient(cfgPlugin) + + assert.Nil(t, c) + assert.EqualError(t, err, "rpc service disabled") +} + +func TestNewClient_WrongRcpConfiguration(t *testing.T) { + cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte("rpc:\n $foo bar")} + assert.NoError(t, cfgPlugin.Init()) + + c, err := rpc.NewClient(cfgPlugin) + + assert.Nil(t, c) + assert.Error(t, err) + assert.Contains(t, err.Error(), "config_plugin_unmarshal_key") +} + +func TestNewClient_ConnectionError(t *testing.T) { + cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte("rpc:\n listen: tcp://127.0.0.1:0")} + assert.NoError(t, cfgPlugin.Init()) + + c, err := rpc.NewClient(cfgPlugin) + + assert.Nil(t, c) + assert.Error(t, err) + assert.Contains(t, err.Error(), "connection refused") +} + +func TestNewClient_SuccessfullyConnected(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + assert.NoError(t, err) + + defer func() { assert.NoError(t, l.Close()) }() + + cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte("rpc:\n listen: tcp://" + l.Addr().String())} + assert.NoError(t, cfgPlugin.Init()) + + c, err := rpc.NewClient(cfgPlugin) + + assert.NotNil(t, c) + assert.NoError(t, err) + + defer func() { assert.NoError(t, c.Close()) }() +} diff --git a/ipc/interface.go b/ipc/interface.go deleted file mode 100644 index 1d70cd21..00000000 --- a/ipc/interface.go +++ /dev/null @@ -1,20 +0,0 @@ -package ipc - -import ( - "context" - "os/exec" - - "github.com/spiral/roadrunner/v2/worker" -) - -// Factory is responsible for wrapping given command into tasks WorkerProcess. -type Factory interface { - // SpawnWorkerWithTimeout creates new WorkerProcess process based on given command with context. - // Process must not be started. - SpawnWorkerWithTimeout(context.Context, *exec.Cmd) (*worker.Process, error) - // SpawnWorker creates new WorkerProcess process based on given command. - // Process must not be started. - SpawnWorker(*exec.Cmd) (*worker.Process, error) - // Close the factory and underlying connections. - Close() error -} diff --git a/ipc/pipe/pipe_factory.go b/ipc/pipe/pipe_factory.go deleted file mode 100755 index 4a3c9a67..00000000 --- a/ipc/pipe/pipe_factory.go +++ /dev/null @@ -1,178 +0,0 @@ -package pipe - -import ( - "context" - "os/exec" - - "github.com/spiral/goridge/v3/pkg/pipe" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/worker" - "go.uber.org/zap" -) - -// Factory connects to stack using standard -// streams (STDIN, STDOUT pipes). -type Factory struct { - log *zap.Logger -} - -// NewPipeFactory returns new factory instance and starts -// listening -func NewPipeFactory(log *zap.Logger) *Factory { - return &Factory{ - log: log, - } -} - -type sr struct { - w *worker.Process - err error -} - -// SpawnWorkerWithTimeout creates new Process and connects it to goridge relay, -// method Wait() must be handled on level above. -func (f *Factory) SpawnWorkerWithTimeout(ctx context.Context, cmd *exec.Cmd) (*worker.Process, error) { - spCh := make(chan sr) - go func() { - w, err := worker.InitBaseWorker(cmd, worker.WithLog(f.log)) - if err != nil { - select { - case spCh <- sr{ - w: nil, - err: err, - }: - return - default: - return - } - } - - in, err := cmd.StdoutPipe() - if err != nil { - select { - case spCh <- sr{ - w: nil, - err: err, - }: - return - default: - return - } - } - - out, err := cmd.StdinPipe() - if err != nil { - select { - case spCh <- sr{ - w: nil, - err: err, - }: - return - default: - return - } - } - - // Init new PIPE relay - relay := pipe.NewPipeRelay(in, out) - w.AttachRelay(relay) - - // Start the worker - err = w.Start() - if err != nil { - select { - case spCh <- sr{ - w: nil, - err: err, - }: - return - default: - return - } - } - - // used as a ping - _, err = internal.Pid(relay) - if err != nil { - _ = w.Kill() - select { - case spCh <- sr{ - w: nil, - err: err, - }: - return - default: - _ = w.Kill() - return - } - } - - select { - case - // return worker - spCh <- sr{ - w: w, - err: nil, - }: - // everything ok, set ready state - w.State().Set(worker.StateReady) - return - default: - _ = w.Kill() - return - } - }() - - select { - case <-ctx.Done(): - return nil, ctx.Err() - case res := <-spCh: - if res.err != nil { - return nil, res.err - } - return res.w, nil - } -} - -func (f *Factory) SpawnWorker(cmd *exec.Cmd) (*worker.Process, error) { - w, err := worker.InitBaseWorker(cmd, worker.WithLog(f.log)) - if err != nil { - return nil, err - } - - in, err := cmd.StdoutPipe() - if err != nil { - return nil, err - } - - out, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - - // Init new PIPE relay - relay := pipe.NewPipeRelay(in, out) - w.AttachRelay(relay) - - // Start the worker - err = w.Start() - if err != nil { - return nil, err - } - - // errors bundle - _, err = internal.Pid(relay) - if err != nil { - _ = w.Kill() - return nil, err - } - - // everything ok, set ready state - w.State().Set(worker.StateReady) - return w, nil -} - -// Close the factory. -func (f *Factory) Close() error { - return nil -} diff --git a/ipc/pipe/pipe_factory_spawn_test.go b/ipc/pipe/pipe_factory_spawn_test.go deleted file mode 100644 index 2ce5a257..00000000 --- a/ipc/pipe/pipe_factory_spawn_test.go +++ /dev/null @@ -1,430 +0,0 @@ -package pipe - -import ( - "os/exec" - "sync" - "testing" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/worker" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -var log = zap.NewNop() - -func Test_GetState2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory(log).SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - assert.Equal(t, worker.StateStopped, w.State().Value()) - }() - - assert.NoError(t, err) - assert.NotNil(t, w) - - assert.Equal(t, worker.StateReady, w.State().Value()) - assert.NoError(t, w.Stop()) -} - -func Test_Kill2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory(log).SpawnWorker(cmd) - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - assert.Error(t, w.Wait()) - assert.Equal(t, worker.StateErrored, w.State().Value()) - }() - - assert.NoError(t, err) - assert.NotNil(t, w) - - assert.Equal(t, worker.StateReady, w.State().Value()) - err = w.Kill() - if err != nil { - t.Errorf("error killing the Process: error %v", err) - } - wg.Wait() -} - -func Test_Pipe_Start2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory(log).SpawnWorker(cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - assert.NoError(t, w.Stop()) -} - -func Test_Pipe_StartError2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - err := cmd.Start() - if err != nil { - t.Errorf("error running the command: error %v", err) - } - - w, err := NewPipeFactory(log).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_PipeError3(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - _, err := cmd.StdinPipe() - if err != nil { - t.Errorf("error creating the STDIN pipe: error %v", err) - } - - w, err := NewPipeFactory(log).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_PipeError4(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - _, err := cmd.StdinPipe() - if err != nil { - t.Errorf("error creating the STDIN pipe: error %v", err) - } - - w, err := NewPipeFactory(log).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_Failboot2(t *testing.T) { - cmd := exec.Command("php", "../../tests/failboot.php") - w, err := NewPipeFactory(log).SpawnWorker(cmd) - assert.Nil(t, w) - assert.Error(t, err) -} - -func Test_Pipe_Invalid2(t *testing.T) { - cmd := exec.Command("php", "../../tests/invalid.php") - w, err := NewPipeFactory(log).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_Echo2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - w, err := NewPipeFactory(log).SpawnWorker(cmd) - assert.NoError(t, err) - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - go func() { - if w.Wait() != nil { - t.Fail() - } - }() - - assert.Equal(t, "hello", res.String()) - err = w.Stop() - assert.NoError(t, err) -} - -func Test_Pipe_Broken2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes") - w, err := NewPipeFactory(log).SpawnWorker(cmd) - assert.NoError(t, err) - require.NotNil(t, w) - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res) - - time.Sleep(time.Second) - err = w.Stop() - assert.Error(t, err) -} - -func Benchmark_Pipe_SpawnWorker_Stop2(b *testing.B) { - f := NewPipeFactory(log) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - w, _ := f.SpawnWorker(cmd) - go func() { - if w.Wait() != nil { - b.Fail() - } - }() - - err := w.Stop() - if err != nil { - b.Errorf("error stopping the worker: error %v", err) - } - } -} - -func Benchmark_Pipe_Worker_ExecEcho2(b *testing.B) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorker(cmd) - sw := worker.From(w) - - b.ReportAllocs() - b.ResetTimer() - go func() { - err := w.Wait() - if err != nil { - b.Errorf("error waiting the worker: error %v", err) - } - }() - defer func() { - err := w.Stop() - if err != nil { - b.Errorf("error stopping the worker: error %v", err) - } - }() - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Pipe_Worker_ExecEcho4(b *testing.B) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - w, err := NewPipeFactory(log).SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Pipe_Worker_ExecEchoWithoutContext2(b *testing.B) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - w, err := NewPipeFactory(log).SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Test_Echo2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory(log).SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - - sw := worker.From(w) - - go func() { - assert.NoError(t, sw.Wait()) - }() - defer func() { - err = sw.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_BadPayload2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorker(cmd) - - sw := worker.From(w) - - go func() { - assert.NoError(t, sw.Wait()) - }() - defer func() { - err := sw.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - res, err := sw.Exec(&payload.Payload{}) - assert.Error(t, err) - assert.Nil(t, res) - - assert.Contains(t, err.Error(), "payload can not be empty") -} - -func Test_String2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - assert.Contains(t, w.String(), "php ../../tests/client.php echo pipes") - assert.Contains(t, w.String(), "ready") - assert.Contains(t, w.String(), "num_execs: 0") -} - -func Test_Echo_Slow2(t *testing.T) { - cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "pipes", "10", "10") - - w, _ := NewPipeFactory(log).SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Broken2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes") - - w, err := NewPipeFactory(log).SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.NotNil(t, err) - assert.Nil(t, res) - - time.Sleep(time.Second * 3) - assert.Error(t, w.Stop()) -} - -func Test_Error2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "error", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.NotNil(t, err) - assert.Nil(t, res) - - if errors.Is(errors.SoftJob, err) == false { - t.Fatal("error should be of type errors.ErrSoftJob") - } - assert.Contains(t, err.Error(), "hello") -} - -func Test_NumExecs2(t *testing.T) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(1), w.State().NumExecs()) - - _, err = sw.Exec(&payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(2), w.State().NumExecs()) - - _, err = sw.Exec(&payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(3), w.State().NumExecs()) -} diff --git a/ipc/pipe/pipe_factory_test.go b/ipc/pipe/pipe_factory_test.go deleted file mode 100755 index 025498b5..00000000 --- a/ipc/pipe/pipe_factory_test.go +++ /dev/null @@ -1,511 +0,0 @@ -package pipe - -import ( - "context" - "os/exec" - "sync" - "testing" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/worker" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_GetState(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - assert.Equal(t, worker.StateStopped, w.State().Value()) - }() - - assert.NoError(t, err) - assert.NotNil(t, w) - - assert.Equal(t, worker.StateReady, w.State().Value()) - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Kill(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - assert.Error(t, w.Wait()) - assert.Equal(t, worker.StateErrored, w.State().Value()) - }() - - assert.NoError(t, err) - assert.NotNil(t, w) - - assert.Equal(t, worker.StateReady, w.State().Value()) - err = w.Kill() - if err != nil { - t.Errorf("error killing the Process: error %v", err) - } - wg.Wait() -} - -func Test_Pipe_Start(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - assert.NoError(t, w.Stop()) -} - -func Test_Pipe_StartError(t *testing.T) { - t.Parallel() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - err := cmd.Start() - if err != nil { - t.Errorf("error running the command: error %v", err) - } - - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_PipeError(t *testing.T) { - t.Parallel() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - _, err := cmd.StdinPipe() - if err != nil { - t.Errorf("error creating the STDIN pipe: error %v", err) - } - - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_PipeError2(t *testing.T) { - t.Parallel() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - // error cause - _, err := cmd.StdinPipe() - if err != nil { - t.Errorf("error creating the STDIN pipe: error %v", err) - } - - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_Failboot(t *testing.T) { - cmd := exec.Command("php", "../../tests/failboot.php") - ctx := context.Background() - - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - - assert.Nil(t, w) - assert.Error(t, err) -} - -func Test_Pipe_Invalid(t *testing.T) { - t.Parallel() - cmd := exec.Command("php", "../../tests/invalid.php") - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Pipe_Echo(t *testing.T) { - t.Parallel() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - go func() { - if w.Wait() != nil { - t.Fail() - } - }() - - assert.Equal(t, "hello", res.String()) -} - -func Test_Pipe_Echo_Script(t *testing.T) { - t.Parallel() - cmd := exec.Command("sh", "../../tests/pipes_test_script.sh") - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - go func() { - if w.Wait() != nil { - t.Fail() - } - }() - - assert.Equal(t, "hello", res.String()) -} - -func Test_Pipe_Broken(t *testing.T) { - t.Parallel() - cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes") - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - require.NoError(t, err) - require.NotNil(t, w) - - go func() { - errW := w.Wait() - require.Error(t, errW) - }() - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res) - - time.Sleep(time.Second) - err = w.Stop() - assert.NoError(t, err) -} - -func Benchmark_Pipe_SpawnWorker_Stop(b *testing.B) { - f := NewPipeFactory(log) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - w, _ := f.SpawnWorkerWithTimeout(context.Background(), cmd) - go func() { - if w.Wait() != nil { - b.Fail() - } - }() - - err := w.Stop() - if err != nil { - b.Errorf("error stopping the worker: error %v", err) - } - } -} - -func Benchmark_Pipe_Worker_ExecEcho(b *testing.B) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(context.Background(), cmd) - sw := worker.From(w) - - b.ReportAllocs() - b.ResetTimer() - go func() { - err := w.Wait() - if err != nil { - b.Errorf("error waiting the worker: error %v", err) - } - }() - defer func() { - err := w.Stop() - if err != nil { - b.Errorf("error stopping the worker: error %v", err) - } - }() - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Pipe_Worker_ExecEcho3(b *testing.B) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Pipe_Worker_ExecEchoWithoutContext(b *testing.B) { - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - ctx := context.Background() - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Test_Echo(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - - sw := worker.From(w) - go func() { - assert.NoError(t, sw.Wait()) - }() - defer func() { - err = sw.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_BadPayload(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - - sw := worker.From(w) - - go func() { - assert.NoError(t, sw.Wait()) - }() - defer func() { - err := sw.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - res, err := sw.Exec(&payload.Payload{}) - - assert.Error(t, err) - assert.Nil(t, res) - - assert.Contains(t, err.Error(), "payload can not be empty") -} - -func Test_String(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - assert.Contains(t, w.String(), "php ../../tests/client.php echo pipes") - assert.Contains(t, w.String(), "ready") - assert.Contains(t, w.String(), "num_execs: 0") -} - -func Test_Echo_Slow(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "pipes", "10", "10") - - w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.Nil(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Broken(t *testing.T) { - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes") - - w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.NotNil(t, err) - assert.Nil(t, res) - - time.Sleep(time.Second * 3) - assert.Error(t, w.Stop()) -} - -func Test_Error(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "error", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.NotNil(t, err) - assert.Nil(t, res) - - if errors.Is(errors.SoftJob, err) == false { - t.Fatal("error should be of type errors.ErrSoftJob") - } - assert.Contains(t, err.Error(), "hello") -} - -func Test_NumExecs(t *testing.T) { - t.Parallel() - ctx := context.Background() - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - - w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err := w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(1), w.State().NumExecs()) - - _, err = sw.Exec(&payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(2), w.State().NumExecs()) - - _, err = sw.Exec(&payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Errorf("fail to execute payload: error %v", err) - } - assert.Equal(t, uint64(3), w.State().NumExecs()) -} diff --git a/ipc/socket/socket_factory.go b/ipc/socket/socket_factory.go deleted file mode 100755 index aa356424..00000000 --- a/ipc/socket/socket_factory.go +++ /dev/null @@ -1,252 +0,0 @@ -package socket - -import ( - "context" - "fmt" - "net" - "os/exec" - "sync" - "time" - - "github.com/shirou/gopsutil/process" - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/pkg/relay" - "github.com/spiral/goridge/v3/pkg/socket" - "github.com/spiral/roadrunner/v2/internal" - "github.com/spiral/roadrunner/v2/worker" - "go.uber.org/zap" - - "golang.org/x/sync/errgroup" -) - -// Factory connects to external stack using socket server. -type Factory struct { - // listens for incoming connections from underlying processes - ls net.Listener - - // relay connection timeout - tout time.Duration - - // sockets which are waiting for process association - relays sync.Map - - log *zap.Logger -} - -// NewSocketServer returns Factory attached to a given socket listener. -// tout specifies for how long factory should serve for incoming relay connection -func NewSocketServer(ls net.Listener, tout time.Duration, log *zap.Logger) *Factory { - f := &Factory{ - ls: ls, - tout: tout, - relays: sync.Map{}, - log: log, - } - - // Be careful - // https://github.com/go101/go101/wiki/About-memory-ordering-guarantees-made-by-atomic-operations-in-Go - // https://github.com/golang/go/issues/5045 - go func() { - err := f.listen() - // there is no logger here, use fmt - if err != nil { - fmt.Printf("[WARN]: socket server listen, error: %v\n", err) - } - }() - - return f -} - -// blocking operation, returns an error -func (f *Factory) listen() error { - errGr := &errgroup.Group{} - errGr.Go(func() error { - for { - conn, err := f.ls.Accept() - if err != nil { - return err - } - - rl := socket.NewSocketRelay(conn) - pid, err := internal.Pid(rl) - if err != nil { - return err - } - - f.attachRelayToPid(pid, rl) - } - }) - - return errGr.Wait() -} - -type socketSpawn struct { - w *worker.Process - err error -} - -// SpawnWorkerWithTimeout creates Process and connects it to appropriate relay or return an error -func (f *Factory) SpawnWorkerWithTimeout(ctx context.Context, cmd *exec.Cmd) (*worker.Process, error) { - c := make(chan socketSpawn) - go func() { - ctxT, cancel := context.WithTimeout(ctx, f.tout) - defer cancel() - w, err := worker.InitBaseWorker(cmd, worker.WithLog(f.log)) - if err != nil { - select { - case c <- socketSpawn{ - w: nil, - err: err, - }: - return - default: - return - } - } - - err = w.Start() - if err != nil { - select { - case c <- socketSpawn{ - w: nil, - err: err, - }: - return - default: - return - } - } - - rl, err := f.findRelayWithContext(ctxT, w) - if err != nil { - _ = w.Kill() - select { - // try to write result - case c <- socketSpawn{ - w: nil, - err: err, - }: - return - // if no receivers - return - default: - return - } - } - - w.AttachRelay(rl) - w.State().Set(worker.StateReady) - - select { - case c <- socketSpawn{ - w: w, - err: nil, - }: - return - default: - _ = w.Kill() - return - } - }() - - select { - case <-ctx.Done(): - return nil, ctx.Err() - case res := <-c: - if res.err != nil { - return nil, res.err - } - - return res.w, nil - } -} - -func (f *Factory) SpawnWorker(cmd *exec.Cmd) (*worker.Process, error) { - w, err := worker.InitBaseWorker(cmd, worker.WithLog(f.log)) - if err != nil { - return nil, err - } - - err = w.Start() - if err != nil { - return nil, err - } - - rl, err := f.findRelay(w) - if err != nil { - _ = w.Kill() - return nil, err - } - - w.AttachRelay(rl) - - // errors bundle - _, err = internal.Pid(rl) - if err != nil { - _ = w.Kill() - return nil, err - } - - w.State().Set(worker.StateReady) - - return w, nil -} - -// Close socket factory and underlying socket connection. -func (f *Factory) Close() error { - return f.ls.Close() -} - -// waits for Process to connect over socket and returns associated relay of timeout -func (f *Factory) findRelayWithContext(ctx context.Context, w worker.BaseProcess) (*socket.Relay, error) { - ticker := time.NewTicker(time.Millisecond * 10) - for { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-ticker.C: - // check for the process exists - _, err := process.NewProcess(int32(w.Pid())) - if err != nil { - return nil, err - } - default: - // find first pid and attach relay to it - var r *socket.Relay - f.relays.Range(func(k, val interface{}) bool { - r = val.(*socket.Relay) - f.relays.Delete(k) - return false - }) - - // no relay exists - if r == nil { - continue - } - - return r, nil - } - } -} - -func (f *Factory) findRelay(w worker.BaseProcess) (*socket.Relay, error) { - const op = errors.Op("factory_find_relay") - // poll every 1ms for the relay - pollDone := time.NewTimer(f.tout) - for { - select { - case <-pollDone.C: - return nil, errors.E(op, errors.Str("relay timeout")) - default: - tmp, ok := f.relays.Load(w.Pid()) - if !ok { - continue - } - return tmp.(*socket.Relay), nil - } - } -} - -// chan to store relay associated with specific pid -func (f *Factory) attachRelayToPid(pid int64, relay relay.Relay) { - f.relays.Store(pid, relay) -} diff --git a/ipc/socket/socket_factory_spawn_test.go b/ipc/socket/socket_factory_spawn_test.go deleted file mode 100644 index 36c6cce2..00000000 --- a/ipc/socket/socket_factory_spawn_test.go +++ /dev/null @@ -1,485 +0,0 @@ -package socket - -import ( - "net" - "os/exec" - "sync" - "syscall" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/worker" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -var log = zap.NewNop() - -func Test_Tcp_Start2(t *testing.T) { - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - errC := ls.Close() - if errC != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Tcp_StartCloseFactory2(t *testing.T) { - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - f := NewSocketServer(ls, time.Minute, log) - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - - w, err := f.SpawnWorker(cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - require.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Tcp_StartError2(t *testing.T) { - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - errC := ls.Close() - if errC != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - err = cmd.Start() - if err != nil { - t.Errorf("error executing the command: error %v", err) - } - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Tcp_Failboot2(t *testing.T) { - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - err3 := ls.Close() - if err3 != nil { - t.Errorf("error closing the listener: error %v", err3) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/failboot.php") - - w, err2 := NewSocketServer(ls, time.Second*5, log).SpawnWorker(cmd) - assert.Nil(t, w) - assert.Error(t, err2) -} - -func Test_Tcp_Invalid2(t *testing.T) { - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - errC := ls.Close() - if errC != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/invalid.php") - - w, err := NewSocketServer(ls, time.Second*1, log).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Tcp_Broken2(t *testing.T) { - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - errC := ls.Close() - if errC != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "broken", "tcp") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - errW := w.Wait() - assert.Error(t, errW) - }() - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res) - wg.Wait() - - time.Sleep(time.Second) - err2 := w.Stop() - // write tcp 127.0.0.1:9007->127.0.0.1:34204: use of closed network connection - // but process exited - assert.NoError(t, err2) -} - -func Test_Tcp_Echo2(t *testing.T) { - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - errC := ls.Close() - if errC != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - - w, _ := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Unix_Start2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err = ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Unix_Failboot2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err = ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../tests/failboot.php") - - w, err := NewSocketServer(ls, time.Second*5, log).SpawnWorker(cmd) - assert.Nil(t, w) - assert.Error(t, err) -} - -func Test_Unix_Timeout2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err = ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "unix", "200", "0") - - w, err := NewSocketServer(ls, time.Millisecond*100, log).SpawnWorker(cmd) - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "relay timeout") -} - -func Test_Unix_Invalid2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err = ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../tests/invalid.php") - - w, err := NewSocketServer(ls, time.Second*10, log).SpawnWorker(cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Unix_Broken2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - errC := ls.Close() - assert.NoError(t, errC) - }() - - cmd := exec.Command("php", "../../tests/client.php", "broken", "unix") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - errW := w.Wait() - assert.Error(t, errW) - }() - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res) - wg.Wait() - - time.Sleep(time.Second) - err = w.Stop() - assert.NoError(t, err) -} - -func Test_Unix_Echo2(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(t, err) - defer func() { - err = ls.Close() - assert.NoError(t, err) - }() - - cmd := exec.Command("php", "../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - if err != nil { - t.Fatal(err) - } - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Benchmark_Tcp_SpawnWorker_Stop2(b *testing.B) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(b, err) - defer func() { - err = ls.Close() - assert.NoError(b, err) - }() - - f := NewSocketServer(ls, time.Minute, log) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - - w, err := f.SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - go func() { - assert.NoError(b, w.Wait()) - }() - - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - } -} - -func Benchmark_Tcp_Worker_ExecEcho2(b *testing.B) { - ls, err := net.Listen("unix", "sock.unix") - assert.NoError(b, err) - defer func() { - err = ls.Close() - assert.NoError(b, err) - }() - - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Unix_SpawnWorker_Stop2(b *testing.B) { - defer func() { - _ = syscall.Unlink("sock.unix") - }() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - errC := ls.Close() - if errC != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - f := NewSocketServer(ls, time.Minute, log) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../tests/client.php", "echo", "unix") - - w, err := f.SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - } -} - -func Benchmark_Unix_Worker_ExecEcho2(b *testing.B) { - defer func() { - _ = syscall.Unlink("sock.unix") - }() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - errC := ls.Close() - if errC != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd) - if err != nil { - b.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} diff --git a/ipc/socket/socket_factory_test.go b/ipc/socket/socket_factory_test.go deleted file mode 100755 index 9b48d233..00000000 --- a/ipc/socket/socket_factory_test.go +++ /dev/null @@ -1,609 +0,0 @@ -package socket - -import ( - "context" - "net" - "os/exec" - "sync" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/worker" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_Tcp_Start(t *testing.T) { - ctx := context.Background() - time.Sleep(time.Millisecond * 10) // to ensure free socket - - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Tcp_StartCloseFactory(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - f := NewSocketServer(ls, time.Minute, log) - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - - w, err := f.SpawnWorkerWithTimeout(ctx, cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - require.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Tcp_StartError(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes") - err = cmd.Start() - if err != nil { - t.Errorf("error executing the command: error %v", err) - } - - serv := NewSocketServer(ls, time.Minute, log) - time.Sleep(time.Second * 2) - w, err := serv.SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Tcp_Failboot(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - err3 := ls.Close() - if err3 != nil { - t.Errorf("error closing the listener: error %v", err3) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/failboot.php") - - w, err2 := NewSocketServer(ls, time.Second*5, log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Nil(t, w) - assert.Error(t, err2) -} - -func Test_Tcp_Timeout(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "tcp", "200", "0") - - w, err := NewSocketServer(ls, time.Millisecond*1, log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "context deadline exceeded") -} - -func Test_Tcp_Invalid(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/invalid.php") - - w, err := NewSocketServer(ls, time.Second*1, log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Tcp_Broken(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - errC := ls.Close() - if errC != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "broken", "tcp") - - w, err := NewSocketServer(ls, time.Second*10, log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - errW := w.Wait() - assert.Error(t, errW) - }() - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res) - wg.Wait() - - time.Sleep(time.Second) - err2 := w.Stop() - // write tcp 127.0.0.1:9007->127.0.0.1:34204: use of closed network connection - // but process is stopped - assert.NoError(t, err2) -} - -func Test_Tcp_Echo(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - - w, _ := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Tcp_Echo_Script(t *testing.T) { - time.Sleep(time.Millisecond * 10) // to ensure free socket - ctx := context.Background() - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if assert.NoError(t, err) { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("sh", "../../tests/socket_test_script.sh") - - w, _ := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd) - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_Unix_Start(t *testing.T) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd) - assert.NoError(t, err) - assert.NotNil(t, w) - - go func() { - assert.NoError(t, w.Wait()) - }() - - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } -} - -func Test_Unix_Failboot(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - ctx := context.Background() - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/failboot.php") - - w, err := NewSocketServer(ls, time.Second*5, log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Nil(t, w) - assert.Error(t, err) -} - -func Test_Unix_Timeout(t *testing.T) { - ls, err := net.Listen("unix", "sock.unix") - ctx := context.Background() - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "unix", "200", "0") - - w, err := NewSocketServer(ls, time.Millisecond*100, log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Nil(t, w) - assert.Error(t, err) - assert.Contains(t, err.Error(), "context deadline exceeded") -} - -func Test_Unix_Invalid(t *testing.T) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/invalid.php") - - w, err := NewSocketServer(ls, time.Second*10, log).SpawnWorkerWithTimeout(ctx, cmd) - assert.Error(t, err) - assert.Nil(t, w) -} - -func Test_Unix_Broken(t *testing.T) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - errC := ls.Close() - if errC != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "broken", "unix") - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - wg := &sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - errW := w.Wait() - assert.Error(t, errW) - }() - - sw := worker.From(w) - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res) - - time.Sleep(time.Second) - err = w.Stop() - assert.NoError(t, err) - - wg.Wait() -} - -func Test_Unix_Echo(t *testing.T) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - t.Errorf("error closing the listener: error %v", err) - } - }() - } else { - t.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - t.Fatal(err) - } - go func() { - assert.NoError(t, w.Wait()) - }() - defer func() { - err = w.Stop() - if err != nil { - t.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Benchmark_Tcp_SpawnWorker_Stop(b *testing.B) { - ctx := context.Background() - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - f := NewSocketServer(ls, time.Minute, log) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - - w, err := f.SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - go func() { - assert.NoError(b, w.Wait()) - }() - - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - } -} - -func Benchmark_Tcp_Worker_ExecEcho(b *testing.B) { - ctx := context.Background() - ls, err := net.Listen("tcp", "127.0.0.1:9007") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} - -func Benchmark_Unix_SpawnWorker_Stop(b *testing.B) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - f := NewSocketServer(ls, time.Minute, log) - for n := 0; n < b.N; n++ { - cmd := exec.Command("php", "../../tests/client.php", "echo", "unix") - - w, err := f.SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - } -} - -func Benchmark_Unix_Worker_ExecEcho(b *testing.B) { - ctx := context.Background() - ls, err := net.Listen("unix", "sock.unix") - if err == nil { - defer func() { - err = ls.Close() - if err != nil { - b.Errorf("error closing the listener: error %v", err) - } - }() - } else { - b.Skip("socket is busy") - } - - cmd := exec.Command("php", "../../tests/client.php", "echo", "unix") - - w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd) - if err != nil { - b.Fatal(err) - } - defer func() { - err = w.Stop() - if err != nil { - b.Errorf("error stopping the Process: error %v", err) - } - }() - - sw := worker.From(w) - - for n := 0; n < b.N; n++ { - if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - } - } -} diff --git a/payload/payload.go b/payload/payload.go deleted file mode 100755 index a475bc75..00000000 --- a/payload/payload.go +++ /dev/null @@ -1,23 +0,0 @@ -package payload - -import ( - "github.com/spiral/roadrunner/v2/utils" -) - -// Payload carries binary header and body to stack and -// back to the server. -type Payload struct { - // Context represent payload context, might be omitted. - Context []byte - - // body contains binary payload to be processed by WorkerProcess. - Body []byte - - // Type of codec used to decode/encode payload - Codec byte -} - -// String returns payload body as string -func (p *Payload) String() string { - return utils.AsString(p.Body) -} diff --git a/plugins.go b/plugins.go new file mode 100644 index 00000000..05080992 --- /dev/null +++ b/plugins.go @@ -0,0 +1,3 @@ +package roadrunner_binary //nolint:revive,stylecheck + +// PLUGINS LOCATED IN THE `internal/container/plugins.go` diff --git a/pool/config.go b/pool/config.go deleted file mode 100644 index 3a058956..00000000 --- a/pool/config.go +++ /dev/null @@ -1,75 +0,0 @@ -package pool - -import ( - "runtime" - "time" -) - -// Config .. Pool config Configures the pool behavior. -type Config struct { - // Debug flag creates new fresh worker before every request. - Debug bool - - // NumWorkers defines how many sub-processes can be run at once. This value - // might be doubled by Swapper while hot-swap. Defaults to number of CPU cores. - NumWorkers uint64 `mapstructure:"num_workers"` - - // MaxJobs defines how many executions is allowed for the worker until - // it's destruction. set 1 to create new process for each new task, 0 to let - // worker handle as many tasks as it can. - MaxJobs uint64 `mapstructure:"max_jobs"` - - // AllocateTimeout defines for how long pool will be waiting for a worker to - // be freed to handle the task. Defaults to 60s. - AllocateTimeout time.Duration `mapstructure:"allocate_timeout"` - - // DestroyTimeout defines for how long pool should be waiting for worker to - // properly destroy, if timeout reached worker will be killed. Defaults to 60s. - DestroyTimeout time.Duration `mapstructure:"destroy_timeout"` - - // Supervision config to limit worker and pool memory usage. - Supervisor *SupervisorConfig `mapstructure:"supervisor"` -} - -// InitDefaults enables default config values. -func (cfg *Config) InitDefaults() { - if cfg.NumWorkers == 0 { - cfg.NumWorkers = uint64(runtime.NumCPU()) - } - - if cfg.AllocateTimeout == 0 { - cfg.AllocateTimeout = time.Minute - } - - if cfg.DestroyTimeout == 0 { - cfg.DestroyTimeout = time.Minute - } - if cfg.Supervisor == nil { - return - } - cfg.Supervisor.InitDefaults() -} - -type SupervisorConfig struct { - // WatchTick defines how often to check the state of worker. - WatchTick time.Duration `mapstructure:"watch_tick"` - - // TTL defines maximum time worker is allowed to live. - TTL time.Duration `mapstructure:"ttl"` - - // IdleTTL defines maximum duration worker can spend in idle mode. Disabled when 0. - IdleTTL time.Duration `mapstructure:"idle_ttl"` - - // ExecTTL defines maximum lifetime per job. - ExecTTL time.Duration `mapstructure:"exec_ttl"` - - // MaxWorkerMemory limits memory per worker. - MaxWorkerMemory uint64 `mapstructure:"max_worker_memory"` -} - -// InitDefaults enables default config values. -func (cfg *SupervisorConfig) InitDefaults() { - if cfg.WatchTick == 0 { - cfg.WatchTick = time.Second - } -} diff --git a/pool/interface.go b/pool/interface.go deleted file mode 100644 index 6a150188..00000000 --- a/pool/interface.go +++ /dev/null @@ -1,59 +0,0 @@ -package pool - -import ( - "context" - - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/worker" -) - -// Pool managed set of inner worker processes. -type Pool interface { - // GetConfig returns pool configuration. - GetConfig() interface{} - - // Exec executes task with payload - Exec(rqs *payload.Payload) (*payload.Payload, error) - - // Workers returns worker list associated with the pool. - Workers() (workers []worker.BaseProcess) - - // RemoveWorker removes worker from the pool. - RemoveWorker(worker worker.BaseProcess) error - - // Reset kill all workers inside the watcher and replaces with new - Reset(ctx context.Context) error - - // Destroy all underlying stack (but let them to complete the task). - Destroy(ctx context.Context) - - // ExecWithContext executes task with context which is used with timeout - execWithTTL(ctx context.Context, rqs *payload.Payload) (*payload.Payload, error) -} - -// Watcher is an interface for the Sync workers lifecycle -type Watcher interface { - // Watch used to add workers to the container - Watch(workers []worker.BaseProcess) error - - // Take takes the first free worker - Take(ctx context.Context) (worker.BaseProcess, error) - - // Release releases the worker putting it back to the queue - Release(w worker.BaseProcess) - - // Allocate - allocates new worker and put it into the WorkerWatcher - Allocate() error - - // Destroy destroys the underlying container - Destroy(ctx context.Context) - - // Reset will replace container and workers array, kill all workers - Reset(ctx context.Context) - - // List return all container w/o removing it from internal storage - List() []worker.BaseProcess - - // Remove will remove worker from the container - Remove(wb worker.BaseProcess) -} diff --git a/pool/static_pool.go b/pool/static_pool.go deleted file mode 100755 index dfd9ffd3..00000000 --- a/pool/static_pool.go +++ /dev/null @@ -1,392 +0,0 @@ -package pool - -import ( - "context" - "os/exec" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/events" - "github.com/spiral/roadrunner/v2/ipc" - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/utils" - "github.com/spiral/roadrunner/v2/worker" - workerWatcher "github.com/spiral/roadrunner/v2/worker_watcher" - "go.uber.org/zap" -) - -const ( - // StopRequest can be sent by worker to indicate that restart is required. - StopRequest = `{"stop":true}` -) - -// ErrorEncoder encode error or make a decision based on the error type -type ErrorEncoder func(err error, w worker.BaseProcess) (*payload.Payload, error) - -type Options func(p *StaticPool) - -type Command func() *exec.Cmd - -// StaticPool controls worker creation, destruction and task routing. Pool uses fixed amount of stack. -type StaticPool struct { - cfg *Config - log *zap.Logger - - // worker command creator - cmd Command - - // creates and connects to stack - factory ipc.Factory - - // manages worker states and TTLs - ww Watcher - - // allocate new worker - allocator worker.Allocator - - // errEncoder is the default Exec error encoder - errEncoder ErrorEncoder -} - -// NewStaticPool creates new worker pool and task multiplexer. StaticPool will initiate with one worker. -func NewStaticPool(ctx context.Context, cmd Command, factory ipc.Factory, cfg *Config, options ...Options) (Pool, error) { - if factory == nil { - return nil, errors.Str("no factory initialized") - } - cfg.InitDefaults() - - if cfg.Debug { - cfg.NumWorkers = 0 - cfg.MaxJobs = 1 - } - - p := &StaticPool{ - cfg: cfg, - cmd: cmd, - factory: factory, - } - - // add pool options - for i := 0; i < len(options); i++ { - options[i](p) - } - - if p.log == nil { - z, err := zap.NewProduction() - if err != nil { - return nil, err - } - - p.log = z - } - - // set up workers allocator - p.allocator = p.newPoolAllocator(ctx, p.cfg.AllocateTimeout, factory, cmd) - // set up workers watcher - p.ww = workerWatcher.NewSyncWorkerWatcher(p.allocator, p.log, p.cfg.NumWorkers, p.cfg.AllocateTimeout) - - // allocate requested number of workers - workers, err := p.allocateWorkers(p.cfg.NumWorkers) - if err != nil { - return nil, err - } - - // add workers to the watcher - err = p.ww.Watch(workers) - if err != nil { - return nil, err - } - - p.errEncoder = defaultErrEncoder(p) - - // if supervised config not nil, guess, that pool wanted to be supervised - if cfg.Supervisor != nil { - sp := supervisorWrapper(p, p.log, p.cfg.Supervisor) - // start watcher timer - sp.Start() - return sp, nil - } - - return p, nil -} - -func WithLogger(z *zap.Logger) Options { - return func(p *StaticPool) { - p.log = z - } -} - -// GetConfig returns associated pool configuration. Immutable. -func (sp *StaticPool) GetConfig() interface{} { - return sp.cfg -} - -// Workers returns worker list associated with the pool. -func (sp *StaticPool) Workers() (workers []worker.BaseProcess) { - return sp.ww.List() -} - -func (sp *StaticPool) RemoveWorker(wb worker.BaseProcess) error { - sp.ww.Remove(wb) - return nil -} - -// Exec executes provided payload on the worker -func (sp *StaticPool) Exec(p *payload.Payload) (*payload.Payload, error) { - const op = errors.Op("static_pool_exec") - if sp.cfg.Debug { - return sp.execDebug(p) - } - ctxGetFree, cancel := context.WithTimeout(context.Background(), sp.cfg.AllocateTimeout) - defer cancel() - w, err := sp.takeWorker(ctxGetFree, op) - if err != nil { - return nil, errors.E(op, err) - } - - rsp, err := w.(worker.SyncWorker).Exec(p) - if err != nil { - return sp.errEncoder(err, w) - } - - // worker want's to be terminated - if len(rsp.Body) == 0 && utils.AsString(rsp.Context) == StopRequest { - sp.stopWorker(w) - return sp.Exec(p) - } - - if sp.cfg.MaxJobs != 0 { - sp.checkMaxJobs(w) - return rsp, nil - } - // return worker back - sp.ww.Release(w) - return rsp, nil -} - -// Destroy all underlying stack (but let them complete the task). -func (sp *StaticPool) Destroy(ctx context.Context) { - sp.ww.Destroy(ctx) -} - -func (sp *StaticPool) Reset(ctx context.Context) error { - // destroy all workers - sp.ww.Reset(ctx) - workers, err := sp.allocateWorkers(sp.cfg.NumWorkers) - if err != nil { - return err - } - // add the NEW workers to the watcher - err = sp.ww.Watch(workers) - if err != nil { - return err - } - - return nil -} - -func defaultErrEncoder(sp *StaticPool) ErrorEncoder { - return func(err error, w worker.BaseProcess) (*payload.Payload, error) { - // just push event if on any stage was timeout error - switch { - case errors.Is(errors.ExecTTL, err): - sp.log.Warn("worker stopped, and will be restarted", zap.String("reason", "execTTL timeout elapsed"), zap.Int64("pid", w.Pid()), zap.String("internal_event_name", events.EventExecTTL.String()), zap.Error(err)) - w.State().Set(worker.StateInvalid) - return nil, err - - case errors.Is(errors.SoftJob, err): - sp.log.Warn("worker stopped, and will be restarted", zap.String("reason", "worker error"), zap.Int64("pid", w.Pid()), zap.String("internal_event_name", events.EventWorkerError.String()), zap.Error(err)) - // if max jobs exceed - if sp.cfg.MaxJobs != 0 && w.State().NumExecs() >= sp.cfg.MaxJobs { - // mark old as invalid and stop - w.State().Set(worker.StateInvalid) - errS := w.Stop() - if errS != nil { - return nil, errors.E(errors.SoftJob, errors.Errorf("err: %v\nerrStop: %v", err, errS)) - } - - return nil, err - } - - // soft jobs errors are allowed, just put the worker back - sp.ww.Release(w) - - return nil, err - case errors.Is(errors.Network, err): - // in case of network error, we can't stop the worker, we should kill it - w.State().Set(worker.StateInvalid) - sp.log.Warn("network error, worker will be restarted", zap.String("reason", "network"), zap.Int64("pid", w.Pid()), zap.String("internal_event_name", events.EventWorkerError.String()), zap.Error(err)) - // kill the worker instead of sending net packet to it - _ = w.Kill() - - return nil, err - default: - w.State().Set(worker.StateInvalid) - sp.log.Warn("worker will be restarted", zap.Int64("pid", w.Pid()), zap.String("internal_event_name", events.EventWorkerDestruct.String()), zap.Error(err)) - // stop the worker, worker here might be in the broken state (network) - errS := w.Stop() - if errS != nil { - return nil, errors.E(errors.Errorf("err: %v\nerrStop: %v", err, errS)) - } - - return nil, err - } - } -} - -// Be careful, sync with pool.Exec method -func (sp *StaticPool) execWithTTL(ctx context.Context, p *payload.Payload) (*payload.Payload, error) { - const op = errors.Op("static_pool_exec_with_context") - if sp.cfg.Debug { - return sp.execDebugWithTTL(ctx, p) - } - - ctxAlloc, cancel := context.WithTimeout(context.Background(), sp.cfg.AllocateTimeout) - defer cancel() - w, err := sp.takeWorker(ctxAlloc, op) - if err != nil { - return nil, errors.E(op, err) - } - - rsp, err := w.(worker.SyncWorker).ExecWithTTL(ctx, p) - if err != nil { - return sp.errEncoder(err, w) - } - - // worker want's to be terminated - if len(rsp.Body) == 0 && utils.AsString(rsp.Context) == StopRequest { - sp.stopWorker(w) - return sp.execWithTTL(ctx, p) - } - - if sp.cfg.MaxJobs != 0 { - sp.checkMaxJobs(w) - return rsp, nil - } - - // return worker back - sp.ww.Release(w) - return rsp, nil -} - -func (sp *StaticPool) stopWorker(w worker.BaseProcess) { - w.State().Set(worker.StateInvalid) - err := w.Stop() - if err != nil { - sp.log.Warn("user requested worker to be stopped", zap.String("reason", "user event"), zap.Int64("pid", w.Pid()), zap.String("internal_event_name", events.EventWorkerError.String()), zap.Error(err)) - } -} - -// checkMaxJobs check for worker number of executions and kill workers if that number more than sp.cfg.MaxJobs -func (sp *StaticPool) checkMaxJobs(w worker.BaseProcess) { - if w.State().NumExecs() >= sp.cfg.MaxJobs { - w.State().Set(worker.StateMaxJobsReached) - sp.ww.Release(w) - return - } - - sp.ww.Release(w) -} - -func (sp *StaticPool) takeWorker(ctxGetFree context.Context, op errors.Op) (worker.BaseProcess, error) { - // Get function consumes context with timeout - w, err := sp.ww.Take(ctxGetFree) - if err != nil { - // if the error is of kind NoFreeWorkers, it means, that we can't get worker from the stack during the allocate timeout - if errors.Is(errors.NoFreeWorkers, err) { - sp.log.Error("no free workers in the pool, wait timeout exceed", zap.String("reason", "no free workers"), zap.String("internal_event_name", events.EventNoFreeWorkers.String()), zap.Error(err)) - return nil, errors.E(op, err) - } - // else if err not nil - return error - return nil, errors.E(op, err) - } - return w, nil -} - -func (sp *StaticPool) newPoolAllocator(ctx context.Context, timeout time.Duration, factory ipc.Factory, cmd func() *exec.Cmd) worker.Allocator { - return func() (worker.SyncWorker, error) { - ctxT, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - w, err := factory.SpawnWorkerWithTimeout(ctxT, cmd()) - if err != nil { - return nil, err - } - - // wrap sync worker - sw := worker.From(w) - - sp.log.Debug("worker is allocated", zap.Int64("pid", sw.Pid()), zap.String("internal_event_name", events.EventWorkerConstruct.String())) - return sw, nil - } -} - -// execDebug used when debug mode was not set and exec_ttl is 0 -func (sp *StaticPool) execDebug(p *payload.Payload) (*payload.Payload, error) { - sw, err := sp.allocator() - if err != nil { - return nil, err - } - - // redirect call to the workers' exec method (without ttl) - r, err := sw.Exec(p) - if err != nil { - return nil, err - } - - go func() { - // read the exit status to prevent process to be a zombie - _ = sw.Wait() - }() - - // destroy the worker - err = sw.Stop() - if err != nil { - sp.log.Debug("debug mode: worker stopped", zap.String("reason", "worker error"), zap.Int64("pid", sw.Pid()), zap.String("internal_event_name", events.EventWorkerError.String()), zap.Error(err)) - return nil, err - } - - return r, nil -} - -// execDebugWithTTL used when user set debug mode and exec_ttl -func (sp *StaticPool) execDebugWithTTL(ctx context.Context, p *payload.Payload) (*payload.Payload, error) { - sw, err := sp.allocator() - if err != nil { - return nil, err - } - - // redirect call to the worker with TTL - r, err := sw.ExecWithTTL(ctx, p) - if err != nil { - return nil, err - } - - go func() { - // read the exit status to prevent process to be a zombie - _ = sw.Wait() - }() - - err = sw.Stop() - if err != nil { - sp.log.Debug("debug mode: worker stopped", zap.String("reason", "worker error"), zap.Int64("pid", sw.Pid()), zap.String("internal_event_name", events.EventWorkerError.String()), zap.Error(err)) - return nil, err - } - - return r, err -} - -// allocate required number of stack -func (sp *StaticPool) allocateWorkers(numWorkers uint64) ([]worker.BaseProcess, error) { - workers := make([]worker.BaseProcess, 0, numWorkers) - - // constant number of stack simplify logic - for i := uint64(0); i < numWorkers; i++ { - w, err := sp.allocator() - if err != nil { - return nil, errors.E(errors.WorkerAllocate, err) - } - - workers = append(workers, w) - } - return workers, nil -} diff --git a/pool/static_pool_test.go b/pool/static_pool_test.go deleted file mode 100755 index 5db2bd86..00000000 --- a/pool/static_pool_test.go +++ /dev/null @@ -1,733 +0,0 @@ -package pool - -import ( - "context" - l "log" - "os/exec" - "runtime" - "strconv" - "sync" - "testing" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/ipc/pipe" - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/utils" - "github.com/spiral/roadrunner/v2/worker" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" -) - -var cfg = &Config{ - NumWorkers: uint64(runtime.NumCPU()), - AllocateTimeout: time.Second * 500, - DestroyTimeout: time.Second * 500, -} - -var log = zap.NewNop() - -func Test_NewPool(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - cfg, - ) - assert.NoError(t, err) - - defer p.Destroy(ctx) - - assert.NotNil(t, p) -} - -func Test_NewPoolReset(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - cfg, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - w := p.Workers() - if len(w) == 0 { - t.Fatal("should be workers inside") - } - - pid := w[0].Pid() - require.NoError(t, p.Reset(context.Background())) - - w2 := p.Workers() - if len(w2) == 0 { - t.Fatal("should be workers inside") - } - - require.NotEqual(t, pid, w2[0].Pid()) - p.Destroy(ctx) -} - -func Test_StaticPool_Invalid(t *testing.T) { - p, err := NewStaticPool( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../tests/invalid.php") }, - pipe.NewPipeFactory(log), - cfg, - ) - - assert.Nil(t, p) - assert.Error(t, err) -} - -func Test_ConfigNoErrorInitDefaults(t *testing.T) { - p, err := NewStaticPool( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.NotNil(t, p) - assert.NoError(t, err) - p.Destroy(context.Background()) -} - -func Test_StaticPool_Echo(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - cfg, - ) - assert.NoError(t, err) - - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - res, err := p.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_StaticPool_Echo_NilContext(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - cfg, - ) - assert.NoError(t, err) - - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - res, err := p.Exec(&payload.Payload{Body: []byte("hello"), Context: nil}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) -} - -func Test_StaticPool_Echo_Context(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "head", "pipes") }, - pipe.NewPipeFactory(log), - cfg, - ) - assert.NoError(t, err) - - defer p.Destroy(ctx) - - assert.NotNil(t, p) - - res, err := p.Exec(&payload.Payload{Body: []byte("hello"), Context: []byte("world")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.Empty(t, res.Body) - assert.NotNil(t, res.Context) - - assert.Equal(t, "world", string(res.Context)) -} - -func Test_StaticPool_JobError(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "error", "pipes") }, - pipe.NewPipeFactory(log), - cfg, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - time.Sleep(time.Second * 2) - - res, err := p.Exec(&payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res) - - if errors.Is(errors.SoftJob, err) == false { - t.Fatal("error should be of type errors.Exec") - } - - assert.Contains(t, err.Error(), "hello") - p.Destroy(ctx) -} - -func Test_StaticPool_Broken_Replace(t *testing.T) { - ctx := context.Background() - - z, err := zap.NewProduction() - require.NoError(t, err) - - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "broken", "pipes") }, - pipe.NewPipeFactory(log), - cfg, - WithLogger(z), - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - time.Sleep(time.Second) - res, err := p.execWithTTL(ctx, &payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res) - - p.Destroy(ctx) -} - -func Test_StaticPool_Broken_FromOutside(t *testing.T) { - ctx := context.Background() - - var cfg2 = &Config{ - NumWorkers: 1, - AllocateTimeout: time.Second * 5, - DestroyTimeout: time.Second * 5, - } - - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - cfg2, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - defer p.Destroy(ctx) - time.Sleep(time.Second) - - res, err := p.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.Equal(t, "hello", res.String()) - assert.Equal(t, 1, len(p.Workers())) - - // first creation - time.Sleep(time.Second * 2) - // killing random worker and expecting pool to replace it - err = p.Workers()[0].Kill() - if err != nil { - t.Errorf("error killing the process: error %v", err) - } - - // re-creation - time.Sleep(time.Second * 2) - list := p.Workers() - for _, w := range list { - assert.Equal(t, worker.StateReady, w.State().Value()) - } -} - -func Test_StaticPool_AllocateTimeout(t *testing.T) { - p, err := NewStaticPool( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "delay", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 1, - AllocateTimeout: time.Nanosecond * 1, - DestroyTimeout: time.Second * 2, - }, - ) - assert.Error(t, err) - if !errors.Is(errors.WorkerAllocate, err) { - t.Fatal("error should be of type WorkerAllocate") - } - assert.Nil(t, p) -} - -func Test_StaticPool_Replace_Worker(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "pid", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 1, - MaxJobs: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - defer p.Destroy(ctx) - // prevent process is not ready - time.Sleep(time.Second) - - var lastPID string - lastPID = strconv.Itoa(int(p.Workers()[0].Pid())) - - res, _ := p.Exec(&payload.Payload{Body: []byte("hello")}) - assert.Equal(t, lastPID, string(res.Body)) - - for i := 0; i < 10; i++ { - res, err := p.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.NotEqual(t, lastPID, string(res.Body)) - lastPID = string(res.Body) - } -} - -func Test_StaticPool_Debug_Worker(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "pid", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - Debug: true, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - defer p.Destroy(ctx) - - // prevent process is not ready - time.Sleep(time.Second) - assert.Len(t, p.Workers(), 0) - - var lastPID string - res, _ := p.Exec(&payload.Payload{Body: []byte("hello")}) - assert.NotEqual(t, lastPID, string(res.Body)) - - assert.Len(t, p.Workers(), 0) - - for i := 0; i < 10; i++ { - assert.Len(t, p.Workers(), 0) - res, err := p.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.NotEqual(t, lastPID, string(res.Body)) - lastPID = string(res.Body) - } -} - -// identical to replace but controlled on worker side -func Test_StaticPool_Stop_Worker(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "stop", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - defer p.Destroy(ctx) - time.Sleep(time.Second) - - var lastPID string - lastPID = strconv.Itoa(int(p.Workers()[0].Pid())) - - res, err := p.Exec(&payload.Payload{Body: []byte("hello")}) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, lastPID, string(res.Body)) - - for i := 0; i < 10; i++ { - res, err := p.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.NoError(t, err) - assert.NotNil(t, res) - assert.NotNil(t, res.Body) - assert.Empty(t, res.Context) - - assert.NotEqual(t, lastPID, string(res.Body)) - lastPID = string(res.Body) - } -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_Destroy_And_Close(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "delay", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.NotNil(t, p) - assert.NoError(t, err) - - p.Destroy(ctx) - _, err = p.Exec(&payload.Payload{Body: []byte("100")}) - assert.Error(t, err) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_Destroy_And_Close_While_Wait(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "delay", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.NotNil(t, p) - assert.NoError(t, err) - - go func() { - _, errP := p.Exec(&payload.Payload{Body: []byte("100")}) - if errP != nil { - t.Errorf("error executing payload: error %v", err) - } - }() - time.Sleep(time.Millisecond * 100) - - p.Destroy(ctx) - _, err = p.Exec(&payload.Payload{Body: []byte("100")}) - assert.Error(t, err) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_Handle_Dead(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../tests/slow-destroy.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 5, - AllocateTimeout: time.Second * 100, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - time.Sleep(time.Second) - for i := range p.Workers() { - p.Workers()[i].State().Set(worker.StateErrored) - } - - _, err = p.Exec(&payload.Payload{Body: []byte("hello")}) - assert.NoError(t, err) - p.Destroy(ctx) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_Slow_Destroy(t *testing.T) { - p, err := NewStaticPool( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "../tests/slow-destroy.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 5, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - p.Destroy(context.Background()) -} - -func Test_StaticPool_NoFreeWorkers(t *testing.T) { - ctx := context.Background() - - p, err := NewStaticPool( - ctx, - // sleep for the 3 seconds - func() *exec.Cmd { return exec.Command("php", "../tests/sleep.php", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - Debug: false, - NumWorkers: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: nil, - }, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - go func() { - _, _ = p.execWithTTL(ctx, &payload.Payload{Body: []byte("hello")}) - }() - - time.Sleep(time.Second) - res, err := p.execWithTTL(ctx, &payload.Payload{Body: []byte("hello")}) - assert.Error(t, err) - assert.Nil(t, res) - - time.Sleep(time.Second) - - p.Destroy(ctx) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_WrongCommand1(t *testing.T) { - p, err := NewStaticPool( - context.Background(), - func() *exec.Cmd { return exec.Command("phg", "../tests/slow-destroy.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 5, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.Error(t, err) - assert.Nil(t, p) -} - -// identical to replace but controlled on worker side -func Test_Static_Pool_WrongCommand2(t *testing.T) { - p, err := NewStaticPool( - context.Background(), - func() *exec.Cmd { return exec.Command("php", "", "echo", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 5, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - - assert.Error(t, err) - assert.Nil(t, p) -} - -func Test_CRC_WithPayload(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/crc_error.php") }, - pipe.NewPipeFactory(log), - cfg, - ) - assert.Error(t, err) - data := err.Error() - assert.Contains(t, data, "warning: some weird php erro") - require.Nil(t, p) -} - -/* PTR: -Benchmark_Pool_Echo-32 49076 29926 ns/op 8016 B/op 20 allocs/op -Benchmark_Pool_Echo-32 47257 30779 ns/op 8047 B/op 20 allocs/op -Benchmark_Pool_Echo-32 46737 29440 ns/op 8065 B/op 20 allocs/op -Benchmark_Pool_Echo-32 51177 29074 ns/op 7981 B/op 20 allocs/op -Benchmark_Pool_Echo-32 51764 28319 ns/op 8012 B/op 20 allocs/op -Benchmark_Pool_Echo-32 54054 30714 ns/op 7987 B/op 20 allocs/op -Benchmark_Pool_Echo-32 54391 30689 ns/op 8055 B/op 20 allocs/op - -VAL: -Benchmark_Pool_Echo-32 47936 28679 ns/op 7942 B/op 19 allocs/op -Benchmark_Pool_Echo-32 49010 29830 ns/op 7970 B/op 19 allocs/op -Benchmark_Pool_Echo-32 46771 29031 ns/op 8014 B/op 19 allocs/op -Benchmark_Pool_Echo-32 47760 30517 ns/op 7955 B/op 19 allocs/op -Benchmark_Pool_Echo-32 48148 29816 ns/op 7950 B/op 19 allocs/op -Benchmark_Pool_Echo-32 52705 29809 ns/op 7979 B/op 19 allocs/op -Benchmark_Pool_Echo-32 54374 27776 ns/op 7947 B/op 19 allocs/op -*/ -func Benchmark_Pool_Echo(b *testing.B) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - cfg, - ) - if err != nil { - b.Fatal(err) - } - - bd := make([]byte, 1024) - c := make([]byte, 1024) - - pld := &payload.Payload{ - Context: c, - Body: bd, - } - - b.ResetTimer() - b.ReportAllocs() - for n := 0; n < b.N; n++ { - if _, err := p.Exec(pld); err != nil { - b.Fail() - } - } -} - -// Benchmark_Pool_Echo_Batched-32 366996 2873 ns/op 1233 B/op 24 allocs/op -// PTR -> Benchmark_Pool_Echo_Batched-32 406839 2900 ns/op 1059 B/op 23 allocs/op -// PTR -> Benchmark_Pool_Echo_Batched-32 413312 2904 ns/op 1067 B/op 23 allocs/op -func Benchmark_Pool_Echo_Batched(b *testing.B) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: uint64(runtime.NumCPU()), - AllocateTimeout: time.Second * 100, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(b, err) - defer p.Destroy(ctx) - - bd := make([]byte, 1024) - c := make([]byte, 1024) - - pld := &payload.Payload{ - Context: c, - Body: bd, - } - - b.ResetTimer() - b.ReportAllocs() - - var wg sync.WaitGroup - for i := 0; i < b.N; i++ { - wg.Add(1) - go func() { - defer wg.Done() - if _, err := p.Exec(pld); err != nil { - b.Fail() - l.Println(err) - } - }() - } - - wg.Wait() -} - -// Benchmark_Pool_Echo_Replaced-32 104/100 10900218 ns/op 52365 B/op 125 allocs/op -func Benchmark_Pool_Echo_Replaced(b *testing.B) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - &Config{ - NumWorkers: 1, - MaxJobs: 1, - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, - ) - assert.NoError(b, err) - defer p.Destroy(ctx) - b.ResetTimer() - b.ReportAllocs() - - for n := 0; n < b.N; n++ { - if _, err := p.Exec(&payload.Payload{Body: []byte("hello")}); err != nil { - b.Fail() - l.Println(err) - } - } -} - -// BenchmarkToStringUnsafe-12 566317729 1.91 ns/op 0 B/op 0 allocs/op -// BenchmarkToStringUnsafe-32 1000000000 0.4434 ns/op 0 B/op 0 allocs/op -func BenchmarkToStringUnsafe(b *testing.B) { - testPayload := []byte("falsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtoj") - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - res := utils.AsString(testPayload) - _ = res - } -} - -// BenchmarkToStringSafe-32 8017846 182.5 ns/op 896 B/op 1 allocs/op -// inline BenchmarkToStringSafe-12 28926276 46.6 ns/op 128 B/op 1 allocs/op -func BenchmarkToStringSafe(b *testing.B) { - testPayload := []byte("falsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtojfalsflasjlifjwpoihejfoiwejow{}{}{}{}jelfjasjfhwaopiehjtopwhtgohrgouahsgkljasdlfjasl;fjals;jdflkndgouwhetopwqhjtoj") - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - res := toStringNotFun(testPayload) - _ = res - } -} - -func toStringNotFun(data []byte) string { - return string(data) -} diff --git a/pool/supervisor_pool.go b/pool/supervisor_pool.go deleted file mode 100755 index 59834859..00000000 --- a/pool/supervisor_pool.go +++ /dev/null @@ -1,247 +0,0 @@ -package pool - -import ( - "context" - "sync" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/events" - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/state/process" - "github.com/spiral/roadrunner/v2/worker" - "go.uber.org/zap" -) - -const ( - MB = 1024 * 1024 -) - -// NSEC_IN_SEC nanoseconds in second -const NSEC_IN_SEC int64 = 1000000000 //nolint:stylecheck - -type Supervised interface { - Pool - // Start used to start watching process for all pool workers - Start() -} - -type supervised struct { - cfg *SupervisorConfig - pool Pool - log *zap.Logger - stopCh chan struct{} - mu *sync.RWMutex -} - -func supervisorWrapper(pool Pool, log *zap.Logger, cfg *SupervisorConfig) *supervised { - sp := &supervised{ - cfg: cfg, - pool: pool, - log: log, - mu: &sync.RWMutex{}, - stopCh: make(chan struct{}), - } - - return sp -} - -func (sp *supervised) execWithTTL(_ context.Context, _ *payload.Payload) (*payload.Payload, error) { - panic("used to satisfy pool interface") -} - -func (sp *supervised) Reset(ctx context.Context) error { - sp.mu.Lock() - defer sp.mu.Unlock() - return sp.pool.Reset(ctx) -} - -func (sp *supervised) Exec(rqs *payload.Payload) (*payload.Payload, error) { - const op = errors.Op("supervised_exec_with_context") - if sp.cfg.ExecTTL == 0 { - sp.mu.RLock() - defer sp.mu.RUnlock() - return sp.pool.Exec(rqs) - } - - ctx, cancel := context.WithTimeout(context.Background(), sp.cfg.ExecTTL) - defer cancel() - - sp.mu.RLock() - defer sp.mu.RUnlock() - res, err := sp.pool.execWithTTL(ctx, rqs) - if err != nil { - return nil, errors.E(op, err) - } - - return res, nil -} - -func (sp *supervised) GetConfig() interface{} { - return sp.pool.GetConfig() -} - -func (sp *supervised) Workers() (workers []worker.BaseProcess) { - sp.mu.Lock() - defer sp.mu.Unlock() - return sp.pool.Workers() -} - -func (sp *supervised) RemoveWorker(worker worker.BaseProcess) error { - return sp.pool.RemoveWorker(worker) -} - -func (sp *supervised) Destroy(ctx context.Context) { - sp.Stop() - sp.mu.Lock() - sp.pool.Destroy(ctx) - sp.mu.Unlock() -} - -func (sp *supervised) Start() { - go func() { - watchTout := time.NewTicker(sp.cfg.WatchTick) - defer watchTout.Stop() - - for { - select { - case <-sp.stopCh: - return - // stop here - case <-watchTout.C: - sp.mu.Lock() - sp.control() - sp.mu.Unlock() - } - } - }() -} - -func (sp *supervised) Stop() { - sp.stopCh <- struct{}{} -} - -func (sp *supervised) control() { //nolint:gocognit - now := time.Now() - - // MIGHT BE OUTDATED - // It's a copy of the Workers pointers - workers := sp.pool.Workers() - - for i := 0; i < len(workers); i++ { - // if worker not in the Ready OR working state - // skip such worker - switch workers[i].State().Value() { - case - worker.StateInvalid, - worker.StateErrored, - worker.StateDestroyed, - worker.StateInactive, - worker.StateStopped, - worker.StateStopping, - worker.StateKilling: - continue - } - - s, err := process.WorkerProcessState(workers[i]) - if err != nil { - // worker not longer valid for supervision - continue - } - - if sp.cfg.TTL != 0 && now.Sub(workers[i].Created()).Seconds() >= sp.cfg.TTL.Seconds() { - /* - worker at this point might be in the middle of request execution: - - ---> REQ ---> WORKER -----------------> RESP (at this point we should not set the Ready state) ------> | ----> Worker gets between supervisor checks and get killed in the ww.Release - ^ - TTL Reached, state - invalid | - -----> Worker Stopped here - */ - - if workers[i].State().Value() != worker.StateWorking { - workers[i].State().Set(worker.StateInvalid) - _ = workers[i].Stop() - } - // just to double check - workers[i].State().Set(worker.StateInvalid) - sp.log.Debug("ttl", zap.String("reason", "ttl is reached"), zap.Int64("pid", workers[i].Pid()), zap.String("internal_event_name", events.EventTTL.String())) - continue - } - - if sp.cfg.MaxWorkerMemory != 0 && s.MemoryUsage >= sp.cfg.MaxWorkerMemory*MB { - /* - worker at this point might be in the middle of request execution: - - ---> REQ ---> WORKER -----------------> RESP (at this point we should not set the Ready state) ------> | ----> Worker gets between supervisor checks and get killed in the ww.Release - ^ - TTL Reached, state - invalid | - -----> Worker Stopped here - */ - - if workers[i].State().Value() != worker.StateWorking { - workers[i].State().Set(worker.StateInvalid) - _ = workers[i].Stop() - } - // just to double check - workers[i].State().Set(worker.StateInvalid) - sp.log.Debug("memory_limit", zap.String("reason", "max memory is reached"), zap.Int64("pid", workers[i].Pid()), zap.String("internal_event_name", events.EventMaxMemory.String())) - continue - } - - // firs we check maxWorker idle - if sp.cfg.IdleTTL != 0 { - // then check for the worker state - if workers[i].State().Value() != worker.StateReady { - continue - } - - /* - Calculate idle time - If worker in the StateReady, we read it LastUsed timestamp as UnixNano uint64 - 2. For example maxWorkerIdle is equal to 5sec, then, if (time.Now - LastUsed) > maxWorkerIdle - we are guessing that worker overlap idle time and has to be killed - */ - - // 1610530005534416045 lu - // lu - now = -7811150814 - nanoseconds - // 7.8 seconds - // get last used unix nano - lu := workers[i].State().LastUsed() - // worker not used, skip - if lu == 0 { - continue - } - - // convert last used to unixNano and sub time.now to seconds - // negative number, because lu always in the past, except for the `back to the future` :) - res := ((int64(lu) - now.UnixNano()) / NSEC_IN_SEC) * -1 - - // maxWorkerIdle more than diff between now and last used - // for example: - // After exec worker goes to the rest - // And resting for the 5 seconds - // IdleTTL is 1 second. - // After the control check, res will be 5, idle is 1 - // 5 - 1 = 4, more than 0, YOU ARE FIRED (removed). Done. - if int64(sp.cfg.IdleTTL.Seconds())-res <= 0 { - /* - worker at this point might be in the middle of request execution: - - ---> REQ ---> WORKER -----------------> RESP (at this point we should not set the Ready state) ------> | ----> Worker gets between supervisor checks and get killed in the ww.Release - ^ - TTL Reached, state - invalid | - -----> Worker Stopped here - */ - - if workers[i].State().Value() != worker.StateWorking { - workers[i].State().Set(worker.StateInvalid) - _ = workers[i].Stop() - } - // just to double-check - workers[i].State().Set(worker.StateInvalid) - sp.log.Debug("idle_ttl", zap.String("reason", "idle ttl is reached"), zap.Int64("pid", workers[i].Pid()), zap.String("internal_event_name", events.EventTTL.String())) - } - } - } -} diff --git a/pool/supervisor_test.go b/pool/supervisor_test.go deleted file mode 100644 index a479671f..00000000 --- a/pool/supervisor_test.go +++ /dev/null @@ -1,437 +0,0 @@ -package pool - -import ( - "context" - "os" - "os/exec" - "testing" - "time" - - "github.com/spiral/roadrunner/v2/ipc/pipe" - "github.com/spiral/roadrunner/v2/payload" - "github.com/spiral/roadrunner/v2/worker" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var cfgSupervised = &Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 100 * time.Second, - ExecTTL: 100 * time.Second, - MaxWorkerMemory: 100, - }, -} - -func TestSupervisedPool_Exec(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/memleak.php", "pipes") }, - pipe.NewPipeFactory(log), - cfgSupervised, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - time.Sleep(time.Second) - - pidBefore := p.Workers()[0].Pid() - - for i := 0; i < 10; i++ { - time.Sleep(time.Second) - _, err = p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - assert.NoError(t, err) - } - - assert.NotEqual(t, pidBefore, p.Workers()[0].Pid()) - - p.Destroy(context.Background()) -} - -func Test_SupervisedPoolReset(t *testing.T) { - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/client.php", "echo", "pipes") }, - pipe.NewPipeFactory(log), - cfgSupervised, - ) - assert.NoError(t, err) - assert.NotNil(t, p) - - w := p.Workers() - if len(w) == 0 { - t.Fatal("should be workers inside") - } - - pid := w[0].Pid() - require.NoError(t, p.Reset(context.Background())) - - w2 := p.Workers() - if len(w2) == 0 { - t.Fatal("should be workers inside") - } - - require.NotEqual(t, pid, w2[0].Pid()) -} - -// This test should finish without freezes -func TestSupervisedPool_ExecWithDebugMode(t *testing.T) { - var cfgSupervised = cfgSupervised - cfgSupervised.Debug = true - - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/supervised.php") }, - pipe.NewPipeFactory(log), - cfgSupervised, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - time.Sleep(time.Second) - - for i := 0; i < 10; i++ { - time.Sleep(time.Second) - _, err = p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - assert.NoError(t, err) - } - - p.Destroy(context.Background()) -} - -func TestSupervisedPool_ExecTTL_TimedOut(t *testing.T) { - var cfgExecTTL = &Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 100 * time.Second, - ExecTTL: 1 * time.Second, - MaxWorkerMemory: 100, - }, - } - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/sleep.php", "pipes") }, - pipe.NewPipeFactory(log), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - defer p.Destroy(context.Background()) - - pid := p.Workers()[0].Pid() - - resp, err := p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.Error(t, err) - assert.Empty(t, resp) - - time.Sleep(time.Second * 1) - // should be new worker with new pid - assert.NotEqual(t, pid, p.Workers()[0].Pid()) -} - -func TestSupervisedPool_ExecTTL_WorkerRestarted(t *testing.T) { - var cfgExecTTL = &Config{ - NumWorkers: uint64(1), - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 5 * time.Second, - }, - } - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/sleep-ttl.php") }, - pipe.NewPipeFactory(log), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - pid := p.Workers()[0].Pid() - - resp, err := p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.NoError(t, err) - assert.Equal(t, string(resp.Body), "hello world") - assert.Empty(t, resp.Context) - - time.Sleep(time.Second) - assert.NotEqual(t, pid, p.Workers()[0].Pid()) - require.Equal(t, p.Workers()[0].State().Value(), worker.StateReady) - pid = p.Workers()[0].Pid() - - resp, err = p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.NoError(t, err) - assert.Equal(t, string(resp.Body), "hello world") - assert.Empty(t, resp.Context) - - time.Sleep(time.Second) - // should be new worker with new pid - assert.NotEqual(t, pid, p.Workers()[0].Pid()) - require.Equal(t, p.Workers()[0].State().Value(), worker.StateReady) - - p.Destroy(context.Background()) -} - -func TestSupervisedPool_Idle(t *testing.T) { - var cfgExecTTL = &Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 1 * time.Second, - ExecTTL: 100 * time.Second, - MaxWorkerMemory: 100, - }, - } - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/idle.php", "pipes") }, - pipe.NewPipeFactory(log), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - pid := p.Workers()[0].Pid() - - resp, err := p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.NoError(t, err) - assert.Empty(t, resp.Body) - assert.Empty(t, resp.Context) - - time.Sleep(time.Second * 5) - - // worker should be marked as invalid and reallocated - rsp, err := p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - assert.NoError(t, err) - require.NotNil(t, rsp) - time.Sleep(time.Second * 2) - require.Len(t, p.Workers(), 1) - // should be new worker with new pid - assert.NotEqual(t, pid, p.Workers()[0].Pid()) - p.Destroy(context.Background()) -} - -func TestSupervisedPool_IdleTTL_StateAfterTimeout(t *testing.T) { - var cfgExecTTL = &Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 1 * time.Second, - IdleTTL: 1 * time.Second, - MaxWorkerMemory: 100, - }, - } - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/exec_ttl.php", "pipes") }, - pipe.NewPipeFactory(log), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - defer p.Destroy(context.Background()) - - pid := p.Workers()[0].Pid() - - time.Sleep(time.Millisecond * 100) - resp, err := p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.NoError(t, err) - assert.Empty(t, resp.Body) - assert.Empty(t, resp.Context) - - time.Sleep(time.Second * 2) - - if len(p.Workers()) < 1 { - t.Fatal("should be at least 1 worker") - return - } - // should be destroyed, state should be Ready, not Invalid - assert.NotEqual(t, pid, p.Workers()[0].Pid()) - assert.Equal(t, int64(1), p.Workers()[0].State().Value()) -} - -func TestSupervisedPool_ExecTTL_OK(t *testing.T) { - var cfgExecTTL = &Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 100 * time.Second, - ExecTTL: 4 * time.Second, - MaxWorkerMemory: 100, - }, - } - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/exec_ttl.php", "pipes") }, - pipe.NewPipeFactory(log), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - defer p.Destroy(context.Background()) - - pid := p.Workers()[0].Pid() - - time.Sleep(time.Millisecond * 100) - resp, err := p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.NoError(t, err) - assert.Empty(t, resp.Body) - assert.Empty(t, resp.Context) - - time.Sleep(time.Second * 1) - // should be the same pid - assert.Equal(t, pid, p.Workers()[0].Pid()) -} - -func TestSupervisedPool_MaxMemoryReached(t *testing.T) { - var cfgExecTTL = &Config{ - NumWorkers: uint64(1), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 100 * time.Second, - IdleTTL: 100 * time.Second, - ExecTTL: 4 * time.Second, - MaxWorkerMemory: 1, - }, - } - - // constructed - // max memory - // constructed - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/memleak.php", "pipes") }, - pipe.NewPipeFactory(log), - cfgExecTTL, - ) - - assert.NoError(t, err) - assert.NotNil(t, p) - - resp, err := p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - assert.NoError(t, err) - assert.Empty(t, resp.Body) - assert.Empty(t, resp.Context) - - time.Sleep(time.Second) - p.Destroy(context.Background()) -} - -func TestSupervisedPool_AllocateFailedOK(t *testing.T) { - var cfgExecTTL = &Config{ - NumWorkers: uint64(2), - AllocateTimeout: time.Second * 15, - DestroyTimeout: time.Second * 5, - Supervisor: &SupervisorConfig{ - WatchTick: 1 * time.Second, - TTL: 5 * time.Second, - }, - } - - ctx := context.Background() - p, err := NewStaticPool( - ctx, - func() *exec.Cmd { return exec.Command("php", "../tests/allocate-failed.php") }, - pipe.NewPipeFactory(log), - cfgExecTTL, - ) - - assert.NoError(t, err) - require.NotNil(t, p) - - time.Sleep(time.Second) - - // should be ok - _, err = p.Exec(&payload.Payload{ - Context: []byte(""), - Body: []byte("foo"), - }) - - require.NoError(t, err) - - // after creating this file, PHP will fail - file, err := os.Create("break") - require.NoError(t, err) - - time.Sleep(time.Second * 5) - assert.NoError(t, file.Close()) - assert.NoError(t, os.Remove("break")) - - defer func() { - if r := recover(); r != nil { - assert.Fail(t, "panic should not be fired!") - } else { - p.Destroy(context.Background()) - } - }() -} diff --git a/priority_queue/binary_heap.go b/priority_queue/binary_heap.go deleted file mode 100644 index fc043927..00000000 --- a/priority_queue/binary_heap.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -binary heap (min-heap) algorithm used as a core for the priority queue -*/ - -package priorityqueue - -import ( - "sync" - "sync/atomic" -) - -type BinHeap struct { - items []Item - // find a way to use pointer to the raw data - len uint64 - maxLen uint64 - cond sync.Cond -} - -func NewBinHeap(maxLen uint64) *BinHeap { - return &BinHeap{ - items: make([]Item, 0, 1000), - len: 0, - maxLen: maxLen, - cond: sync.Cond{L: &sync.Mutex{}}, - } -} - -func (bh *BinHeap) fixUp() { - k := bh.len - 1 - p := (k - 1) >> 1 // k-1 / 2 - - for k > 0 { - cur, par := (bh.items)[k], (bh.items)[p] - - if cur.Priority() < par.Priority() { - bh.swap(k, p) - k = p - p = (k - 1) >> 1 - } else { - return - } - } -} - -func (bh *BinHeap) swap(i, j uint64) { - (bh.items)[i], (bh.items)[j] = (bh.items)[j], (bh.items)[i] -} - -func (bh *BinHeap) fixDown(curr, end int) { - cOneIdx := (curr << 1) + 1 - for cOneIdx <= end { - cTwoIdx := -1 - if (curr<<1)+2 <= end { - cTwoIdx = (curr << 1) + 2 - } - - idxToSwap := cOneIdx - if cTwoIdx > -1 && (bh.items)[cTwoIdx].Priority() < (bh.items)[cOneIdx].Priority() { - idxToSwap = cTwoIdx - } - if (bh.items)[idxToSwap].Priority() < (bh.items)[curr].Priority() { - bh.swap(uint64(curr), uint64(idxToSwap)) - curr = idxToSwap - cOneIdx = (curr << 1) + 1 - } else { - return - } - } -} - -func (bh *BinHeap) Len() uint64 { - return atomic.LoadUint64(&bh.len) -} - -func (bh *BinHeap) Insert(item Item) { - bh.cond.L.Lock() - - // check the binary heap len before insertion - if bh.Len() > bh.maxLen { - // unlock the mutex to proceed to get-max - bh.cond.L.Unlock() - - // signal waiting goroutines - for bh.Len() > 0 { - // signal waiting goroutines - bh.cond.Signal() - } - // lock mutex to proceed inserting into the empty slice - bh.cond.L.Lock() - } - - bh.items = append(bh.items, item) - - // add len to the slice - atomic.AddUint64(&bh.len, 1) - - // fix binary heap up - bh.fixUp() - bh.cond.L.Unlock() - - // signal the goroutine on wait - bh.cond.Signal() -} - -func (bh *BinHeap) ExtractMin() Item { - bh.cond.L.Lock() - - // if len == 0, wait for the signal - for bh.Len() == 0 { - bh.cond.Wait() - } - - bh.swap(0, bh.len-1) - - item := (bh.items)[int(bh.len)-1] - bh.items = (bh).items[0 : int(bh.len)-1] - bh.fixDown(0, int(bh.len-2)) - - // reduce len - atomic.AddUint64(&bh.len, ^uint64(0)) - - bh.cond.L.Unlock() - return item -} diff --git a/priority_queue/binary_heap_test.go b/priority_queue/binary_heap_test.go deleted file mode 100644 index e29835c2..00000000 --- a/priority_queue/binary_heap_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package priorityqueue - -import ( - "fmt" - "math/rand" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -type Test int - -func (t Test) Body() []byte { - return nil -} - -func (t Test) Context() ([]byte, error) { - return nil, nil -} - -func (t Test) ID() string { - return "none" -} - -func (t Test) Priority() int64 { - return int64(t) -} - -func TestBinHeap_Init(t *testing.T) { - a := []Item{Test(2), Test(23), Test(33), Test(44), Test(1), Test(2), Test(2), Test(2), Test(4), Test(6), Test(99)} - - bh := NewBinHeap(12) - - for i := 0; i < len(a); i++ { - bh.Insert(a[i]) - } - - expected := []Item{Test(1), Test(2), Test(2), Test(2), Test(2), Test(4), Test(6), Test(23), Test(33), Test(44), Test(99)} - - res := make([]Item, 0, 12) - - for i := 0; i < 11; i++ { - item := bh.ExtractMin() - res = append(res, item) - } - - require.Equal(t, expected, res) -} - -func TestBinHeap_MaxLen(t *testing.T) { - a := []Item{Test(2), Test(23), Test(33), Test(44), Test(1), Test(2), Test(2), Test(2), Test(4), Test(6), Test(99)} - - bh := NewBinHeap(1) - - go func() { - res := make([]Item, 0, 12) - - for i := 0; i < 11; i++ { - item := bh.ExtractMin() - res = append(res, item) - } - require.Equal(t, 11, len(res)) - return - }() - - time.Sleep(time.Second) - for i := 0; i < len(a); i++ { - bh.Insert(a[i]) - } - - time.Sleep(time.Second) -} - -func TestNewPriorityQueue(t *testing.T) { - insertsPerSec := uint64(0) - getPerSec := uint64(0) - stopCh := make(chan struct{}, 1) - pq := NewBinHeap(1000) - - go func() { - tt3 := time.NewTicker(time.Millisecond * 10) - for { - select { - case <-tt3.C: - require.Less(t, pq.Len(), uint64(1002)) - case <-stopCh: - return - } - } - }() - - go func() { - tt := time.NewTicker(time.Second) - - for { - select { - case <-tt.C: - fmt.Println(fmt.Sprintf("Insert per second: %d", atomic.LoadUint64(&insertsPerSec))) - atomic.StoreUint64(&insertsPerSec, 0) - fmt.Println(fmt.Sprintf("ExtractMin per second: %d", atomic.LoadUint64(&getPerSec))) - atomic.StoreUint64(&getPerSec, 0) - case <-stopCh: - tt.Stop() - return - } - } - }() - - go func() { - for { - select { - case <-stopCh: - return - default: - pq.ExtractMin() - atomic.AddUint64(&getPerSec, 1) - } - } - }() - - go func() { - for { - select { - case <-stopCh: - return - default: - pq.Insert(Test(rand.Int())) //nolint:gosec - atomic.AddUint64(&insertsPerSec, 1) - } - } - }() - - time.Sleep(time.Second * 5) - stopCh <- struct{}{} - stopCh <- struct{}{} - stopCh <- struct{}{} - stopCh <- struct{}{} -} diff --git a/priority_queue/interface.go b/priority_queue/interface.go deleted file mode 100644 index 42510f96..00000000 --- a/priority_queue/interface.go +++ /dev/null @@ -1,22 +0,0 @@ -package priorityqueue - -type Queue interface { - Insert(item Item) - ExtractMin() Item - Len() uint64 -} - -// Item represents binary heap item -type Item interface { - // ID is a unique item identifier - ID() string - - // Priority returns the Item's priority to sort - Priority() int64 - - // Body is the Item payload - Body() []byte - - // Context is the Item meta information - Context() ([]byte, error) -} diff --git a/schemas/config/1.0.schema.json b/schemas/config/1.0.schema.json new file mode 100644 index 00000000..df4292af --- /dev/null +++ b/schemas/config/1.0.schema.json @@ -0,0 +1,278 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Version 1.0 is deprecated. Please, upgrade RR up to version 2", + "type": "object", + "properties": { + "env": { + "type": "object", + "properties": { + "key": { + "type": "string" + } + } + }, + "rpc": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "listen": { + "type": "string" + } + } + }, + "metrics": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "collect": { + "type": "object", + "patternProperties": { + "[a-zA-Z0-9-_]": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "help": { + "type": "string" + }, + "labels": { + "type": "array", + "items": {} + }, + "buckets": { + "type": "array", + "items": {} + } + } + } + } + } + } + }, + "http": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "ssl": { + "type": "object", + "properties": { + "port": { + "type": "integer" + }, + "redirect": { + "type": "boolean" + }, + "cert": { + "type": "string" + }, + "key": { + "type": "string" + }, + "rootCa": { + "type": "string" + } + } + }, + "fcgi": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "http2": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "h2c": { + "type": "boolean" + }, + "maxConcurrentStreams": { + "type": "integer" + } + } + }, + "maxRequestSize": { + "type": "integer" + }, + "uploads": { + "type": "object", + "properties": { + "forbid": { + "type": "array", + "items": {} + } + } + }, + "trustedSubnets": { + "type": "array", + "items": {} + }, + "workers": { + "type": "object", + "properties": { + "command": { + "type": "string" + }, + "relay": { + "type": "string" + }, + "user": { + "type": "string" + }, + "pool": { + "type": "object", + "properties": { + "numWorkers": { + "type": "integer" + }, + "maxJobs": { + "type": "integer" + }, + "allocateTimeout": { + "type": "integer" + }, + "destroyTimeout": { + "type": "integer" + } + } + } + } + } + } + }, + "headers": { + "type": "object", + "properties": { + "cors": { + "type": "object", + "properties": { + "allowedOrigin": { + "type": "string" + }, + "allowedHeaders": { + "type": "string" + }, + "allowedMethods": { + "type": "string" + }, + "allowCredentials": { + "type": "boolean" + }, + "exposedHeaders": { + "type": "string" + }, + "maxAge": { + "type": "integer" + } + } + }, + "request": { + "type": "object", + "patternProperties": { + "[a-zA-Z0-9-_]": { + "type": "string" + } + } + }, + "response": { + "type": "object", + "patternProperties": { + "[a-zA-Z0-9-_]": { + "type": "string" + } + } + } + } + }, + "limit": { + "type": "object", + "properties": { + "interval": { + "type": "integer" + }, + "services": { + "type": "object", + "properties": { + "http": { + "type": "object", + "properties": { + "maxMemory": { + "type": "integer" + }, + "TTL": { + "type": "integer" + }, + "idleTTL": { + "type": "integer" + }, + "execTTL": { + "type": "integer" + } + } + } + } + } + } + }, + "static": { + "type": "object", + "properties": { + "dir": { + "type": "string" + }, + "forbid": { + "type": "array", + "items": {} + } + } + }, + "health": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "reload": { + "type": "object", + "properties": { + "interval": { + "type": "string" + }, + "patterns": { + "type": "array", + "items": {} + }, + "services": { + "type": "object", + "properties": { + "http": { + "type": "object", + "properties": { + "dirs": { + "type": "array", + "items": {} + }, + "recursive": { + "type": "boolean" + } + } + } + } + } + } + } + } +} diff --git a/schemas/config/2.0.schema.json b/schemas/config/2.0.schema.json new file mode 100644 index 00000000..76b8f94d --- /dev/null +++ b/schemas/config/2.0.schema.json @@ -0,0 +1,1806 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Spiral Roadrunner config file schema version 2", + "description": "Spiral Roadrunner config file schema version 2.", + "type": "object", + "additionalProperties": true, + "minProperties": 1, + "required": [ + "server" + ], + "properties": { + "rpc": { + "type": "object", + "properties": { + "listen": { + "description": "TCP address:port for listening", + "type": "string", + "default": "tcp://127.0.0.1:6001", + "examples": [ + "tcp://127.0.0.1:6001" + ], + "pattern": "^tcp:\/\/[0-9a-zA-Z_.-]+:[0-9]{1,5}$" + } + } + }, + "server": { + "type": "object", + "properties": { + "on_init": { + "description": "Execute command or script before RR starts allocating workers", + "type": "object", + "properties": { + "command": { + "description": "Command to execute. It can be script or binary", + "type": "string", + "examples": [ + "php not-worker.php", + "sh script.sh", + "start script.bat" + ] + }, + "exec_timeout": { + "description": "Script execute timeout", + "$ref": "#/definitions/Duration", + "default": "60s" + }, + "env": { + "description": "Environment variables for the worker processes", + "type": "array", + "items": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "command": { + "description": "Worker starting command, with any required arguments", + "type": "string", + "examples": [ + "php psr-worker.php" + ] + }, + "user": { + "description": "User name (not UID) for the worker processes. An empty value means to use the RR process user", + "type": "string", + "default": "", + "examples": [ + "www-data" + ] + }, + "group": { + "description": "Group name (not GID) for the worker processes. An empty value means to use the RR process user", + "type": "string", + "default": "", + "examples": [ + "www-data" + ] + }, + "env": { + "description": "Environment variables for the worker processes", + "type": "array", + "items": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "relay": { + "description": "Worker relay can be: 'pipes', TCP (eg.: tcp://127.0.0.1:6002), or socket (eg.: unix:///var/run/rr.sock)", + "type": "string", + "default": "pipes", + "examples": [ + "pipes", + "tcp://127.0.0.1:6002", + "unix:///var/run/rr.sock" + ] + }, + "relay_timeout": { + "description": "Timeout for relay connection establishing (only for socket and TCP port relay)", + "$ref": "#/definitions/Duration", + "default": "60s" + } + }, + "required": [ + "command" + ] + }, + "logs": { + "type": "object", + "properties": { + "mode": { + "$ref": "#/definitions/LogMode", + "default": "development" + }, + "level": { + "$ref": "#/definitions/LogLevel", + "default": "debug" + }, + "encoding": { + "$ref": "#/definitions/LogEncoding", + "default": "console" + }, + "output": { + "description": "Output", + "$ref": "#/definitions/LogOutput", + "default": "stderr" + }, + "err_output": { + "description": "Errors only output", + "$ref": "#/definitions/LogOutput", + "default": "stderr" + }, + "channels": { + "description": "You can configure each plugin log messages individually", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "mode": { + "$ref": "#/definitions/LogMode" + }, + "level": { + "$ref": "#/definitions/LogLevel" + }, + "encoding": { + "$ref": "#/definitions/LogEncoding" + }, + "output": { + "$ref": "#/definitions/LogOutput" + }, + "err_output": { + "$ref": "#/definitions/LogOutput" + } + } + } + } + } + }, + "temporal": { + "description": "Workflow and activity mesh service, https://docs.temporal.io/docs/php/introduction/", + "type": "object", + "properties": { + "address": { + "description": "Address of temporal server", + "type": "string", + "default": "127.0.0.1:7233" + }, + "cache_size": { + "description": "Sticky cache size. Sticky workflow execution is the affinity between workflow tasks of a specific workflow execution to a specific worker. The benefit of sticky execution is that the workflow does not have to reconstruct state by replaying history from the beginning. The cache is shared between workers running within same process. This must be called before any worker is started. If not called, the default size of 10K (which may change) will be used", + "type": "integer", + "default": 10000 + }, + "namespace": { + "description": "Namespace name for this client to work with", + "type": "string", + "default": "default" + }, + "codec": { + "description": "Internal temporal communication protocol", + "type": "string", + "default": "proto", + "anyOf": [ + { + "type": "string", + "examples": [ + "proto", + "json" + ] + } + ] + }, + "debug_level": { + "description": "Debugging level (only for json codec)", + "type": "integer", + "minimum": 0, + "maximum": 2, + "default": 0 + }, + "metrics": { + "description": "Temporal metrics", + "type": "object", + "default": null, + "properties": { + "address": { + "description": "Server metrics address", + "type": "string", + "default": "127.0.0.1:9091" + }, + "type": { + "type": "string", + "description": "Metrics type", + "anyOf": [ + { + "type": "string", + "examples": [ + "summary", + "histogram" + ] + } + ] + }, + "prefix": { + "description": "Temporal metrics prefix", + "type": "string", + "default": null + } + } + }, + "activities": { + "description": "Activities pool settings", + "type": "object", + "$ref": "#/definitions/WorkersPool" + } + } + }, + "kv": { + "description": "Key value storages plugin", + "type": "object", + "minProperties": 1, + "patternProperties": { + "[a-zA-Z0-9_-]*": { + "anyOf": [ + { + "type": "object", + "description": "boltdb driver", + "properties": { + "driver": { + "type": "string", + "description": "Driver which should be used for the storage" + }, + "config": { + "anyOf": [ + { + "type": "object", + "$ref": "#/definitions/BoltDB" + }, + { + "type": "object", + "$ref": "#/definitions/Memcached" + }, + { + "type": "object", + "$ref": "#/definitions/Redis" + }, + { + "type": "object", + "$ref": "#/definitions/Memory" + } + ] + } + }, + "required": [ + "driver" + ] + } + ] + } + } + }, + "service": { + "description": "Service plugin settings", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "allOf": [ + { + "description": "User defined services", + "type": "object", + "$ref": "#/definitions/Service" + } + ] + } + } + }, + "http": { + "type": "object", + "properties": { + "address": { + "description": "Host and port to listen on", + "$ref": "#/definitions/HostAndPort", + "examples": [ + "127.0.0.1:8080", + ":8080" + ] + }, + "max_request_size": { + "description": "Maximal incoming request size in megabytes. Zero means no limit", + "type": "integer", + "minimum": 0, + "default": 0 + }, + "middleware": { + "description": "Middleware for the http plugin, order is important", + "type": "array", + "items": { + "type": "string", + "enum": [ + "headers", + "gzip", + "static", + "websockets", + "sendfile", + "new_relic", + "http_metrics", + "cache" + ], + "pattern": "^[0-9a-zA-Z_]+$" + } + }, + "trusted_subnets": { + "description": "Allow incoming requests only from the following subnets", + "type": "array", + "items": { + "type": "string", + "examples": [ + "10.0.0.0/8", + "127.0.0.0/8" + ] + }, + "default": [ + "10.0.0.0/8", + "127.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "::1/128", + "fc00::/7", + "fe80::/10" + ] + }, + "cache": { + "description": "RFC 7234 cache middleware", + "type": "object", + "properties": { + "driver": { + "description": "Driver to store cached responses", + "type": "string", + "items": { + "maxItems": 1, + "type": "string", + "enum": [ + "memory" + ] + }, + "default": "memory" + }, + "cache_methods": { + "type": "array", + "default": "GET", + "items": { + "type": "string", + "description": "HTTP methods to cache", + "enum": [ + "GET", + "POST", + "HEAD" + ] + } + }, + "config": { + "description": "Cache driver configuration", + "type": "object" + } + }, + "required": [ + "config" + ] + }, + "new_relic": { + "description": "New Relic middleware", + "type": "object", + "properties": { + "app_name": { + "type": "string", + "description": "Application name. NEW_RELIC_APP_NAME env variable should be set if the app_name key is empty.", + "default": null + }, + "licence_key": { + "type": "string", + "description": "Licence key. NEW_RELIC_LICENSE_KEY env variable should be set if the license_key key is empty.", + "default": null + } + } + }, + "uploads": { + "type": "object", + "properties": { + "dir": { + "description": "Directory for file uploads. Empty value means to use $TEMP based on your OS", + "type": "string", + "examples": [ + "/tmp" + ], + "default": "" + }, + "forbid": { + "description": "Deny files with the following extensions to upload", + "type": "array", + "items": { + "type": "string", + "examples": [ + ".php", + ".exe" + ] + }, + "default": [ + ".php", + ".exe", + ".bat" + ] + }, + "allow": { + "description": "Allow files with the following extensions to upload", + "type": "array", + "items": { + "type": "string", + "examples": [ + ".html", + ".go" + ] + }, + "default": "" + } + } + }, + "headers": { + "description": "HTTP headers map", + "type": "object", + "properties": { + "cors": { + "description": "Allows to control CORS headers", + "type": "object", + "properties": { + "allowed_origin": { + "description": "Controls 'Access-Control-Allow-Origin' header value", + "type": "string", + "examples": [ + "*" + ], + "default": "" + }, + "allowed_headers": { + "description": "Controls 'Access-Control-Allow-Headers' header value", + "type": "string", + "examples": [ + "*" + ], + "default": "" + }, + "allowed_methods": { + "description": "Controls 'Access-Control-Allow-Methods' header value", + "type": "string", + "examples": [ + "GET,POST,PUT,DELETE" + ], + "default": "" + }, + "allow_credentials": { + "description": "Controls 'Access-Control-Allow-Credentials' header value", + "type": "boolean", + "default": false + }, + "exposed_headers": { + "description": "Controls 'Access-Control-Expose-Headers' header value", + "type": "string", + "examples": [ + "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma" + ], + "default": "" + }, + "max_age": { + "description": "Controls 'Access-Control-Max-Age' header value (in seconds)", + "type": "integer", + "examples": [ + 600 + ], + "default": 0 + } + } + }, + "request": { + "description": "Automatically add headers to every request passed to PHP", + "$ref": "#/definitions/Hashmap" + }, + "response": { + "description": "Automatically add headers to every response", + "$ref": "#/definitions/Hashmap" + } + } + }, + "static": { + "description": "Static assets serving settings", + "type": "object", + "properties": { + "dir": { + "description": "Path to the directory with static assets", + "type": "string", + "examples": [ + ".", + "/var/www/html" + ] + }, + "forbid": { + "description": "File extensions that should not be served", + "type": "array", + "items": { + "type": "string", + "examples": [ + ".php", + ".htaccess" + ] + } + }, + "allow": { + "description": "File extensions which should be served", + "type": "array", + "items": { + "type": "string", + "examples": [ + ".php", + ".htaccess" + ] + } + }, + "calculate_etag": { + "description": "Turn on etag computation for the static file", + "type": "boolean" + }, + "weak": { + "description": "Use a weak generator (/W), it uses only filename to generate a CRC32 sum", + "type": "boolean" + }, + "response": { + "description": "Custom headers for the static files", + "$ref": "#/definitions/Hashmap" + } + }, + "required": [ + "dir" + ] + }, + "pool": { + "description": "Workers pool settings", + "$ref": "#/definitions/WorkersPool" + }, + "ssl": { + "description": "SSL (Secure Sockets Layer) settings", + "type": "object", + "properties": { + "address": { + "description": "Host and port to listen on", + "$ref": "#/definitions/HostAndPort", + "examples": [ + "127.0.0.1:443", + ":8443" + ] + }, + "acme": { + "description": "ACME certificates provider (Let's encrypt)", + "type": "object", + "properties": { + "certs_dir": { + "description": "Directory to use as a certificate/pk, account info storage", + "type": "string", + "default": "rr_cache" + }, + "email": { + "description": "User email, used to create LE account", + "type": "string" + }, + "alt_http_port": { + "description": "Alternate port for the http challenge. Challenge traffic should be redirected to this port if overridden.", + "type": "integer", + "default": 80 + }, + "alt_tlsalpn_port": { + "description": "Alternate port for the tls-alpn-01 challenge. Challenge traffic should be redirected to this port if overridden.", + "type": "integer", + "default": 443 + }, + "challenge_type": { + "type": "string", + "enum": [ + "http-01", + "tlsalpn-01" + ], + "description": "Challenge types", + "default": "http-01" + }, + "use_production_endpoint": { + "description": "Use production or staging endpoint. NOTE, try to use staging endpoint to make sure, that everything works correctly.", + "type": "boolean", + "default": false + }, + "domains": { + "type": "array", + "description": "List of your domains to obtain certificates" + } + }, + "required": [ + "domains", + "email" + ] + }, + "redirect": { + "description": "Automatic redirect from http to https schema", + "type": "boolean", + "default": false + }, + "cert": { + "description": "Path to the cert file", + "type": "string", + "minLength": 1, + "examples": [ + "/ssl/server.crt" + ] + }, + "key": { + "description": "Path to the cert key file", + "type": "string", + "minLength": 1, + "examples": [ + "/ssl/server.key" + ] + }, + "root_ca": { + "description": "Path to the root certificate authority file", + "type": "string", + "minLength": 1, + "examples": [ + "/ssl/root.crt" + ] + } + }, + "required": [ + "address", + "cert", + "key" + ] + }, + "fcgi": { + "description": "FastCGI frontend support", + "type": "object", + "properties": { + "address": { + "description": "FastCGI connection DSN. Supported TCP and Unix sockets. An empty value disables this", + "type": "string", + "examples": [ + "tcp://0.0.0.0:7921" + ] + } + }, + "required": [ + "address" + ] + }, + "http2": { + "description": "HTTP/2 settings", + "type": "object", + "properties": { + "h2c": { + "description": "HTTP/2 over non-encrypted TCP connection using H2C", + "type": "boolean", + "default": false + }, + "max_concurrent_streams": { + "description": "Maximal concurrent streams count", + "type": "integer", + "default": 128, + "minimum": 0 + } + } + } + }, + "required": [ + "address" + ] + }, + "redis": { + "description": "Redis section. Should be defined to use as a broadcast driver for the websockets (with no limitation to use in other plugins).", + "type": "object", + "$ref": "#/definitions/Redis" + }, + "websockets": { + "description": "Websockets plugin, HTTP middleware", + "type": "object", + "properties": { + "broker": { + "type": "string", + "pattern": "^[a-zA-Z0-9._-]+$", + "description": "Broker to use. Brokers can be set in the broadcast plugin. For example, if you use broker: default here, broadcast plugin should have default broker in its config." + }, + "allowed_origin": { + "type": "string", + "description": " Allowed request origin (single value). This option is optional (allowed all by default)", + "default": "*" + }, + "path": { + "type": "string", + "description": "http path where to handle websockets connections", + "default": "/ws" + } + } + }, + "broadcast": { + "description": " Broadcast plugin. It main purpose is to broadcast published messages via all brokers. Use it in conjunction with the websockets, memory and redis plugins. LIMITATION: DO NOT use the same redis connection within different sections or messages will be duplicated. There is no limitation to use different redis connections (ie localhost:6379, localhost:6378, etc) in different sections.", + "type": "object", + "minProperties": 1, + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "description": "Driver configuration sections", + "type": "object", + "properties": { + "driver": { + "description": "Driver to use. Available drivers: redis, memory. In-memory driver does not require any configuration.", + "type": "string", + "enum": [ + "memory", + "redis" + ] + }, + "config": { + "description": "Driver configuration", + "type": "object", + "allOf": [ + { + "description": "In-memory configuration", + "type": "object", + "properties": {} + }, + { + "type": "object", + "$ref": "#/definitions/Redis" + } + ] + } + }, + "required": [ + "driver", + "config" + ] + } + } + }, + "metrics": { + "description": "Application metrics in Prometheus format (docs: https://roadrunner.dev/docs/beep-beep-metrics)", + "type": "object", + "properties": { + "address": { + "description": "Prometheus client address (path /metrics added automatically).", + "type": "string", + "default": "127.0.0.1:2112" + }, + "collect": { + "description": "Application-specific metrics (published using an RPC connection to the server)", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "histogram", + "gauge", + "counter", + "summary" + ] + }, + "help": { + "type": "string", + "description": "Help message" + }, + "labels": { + "type": "array", + "minItems": 1, + "description": "Metrics labels" + }, + "buckets": { + "type": "array", + "items": { + "type": "number" + } + }, + "objectives": { + "title": "map[float]float", + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } + } + } + } + } + } + } + }, + "status": { + "description": "Health check endpoint (docs: https://roadrunner.dev/docs/beep-beep-health). If response code is 200 - it means at least one worker ready to serve requests. 500 - there are no workers ready to service requests.", + "type": "object", + "properties": { + "address": { + "description": "Host and port to listen on (eg.: `127.0.0.1:2114`). Use the following URL: http://127.0.0.1:2114/health?plugin=http. Multiple plugins must be separated using & - http://127.0.0.1:2114/health?plugin=http&plugin=rpc where http and rpc are active (connected) plugins.", + "type": "string", + "examples": [ + "127.0.0.1:2114" + ] + }, + "unavailable_status_code": { + "description": "Response status code if a requested plugin not ready to handle requests. Valid for both /health and /ready endpoints", + "type": "integer", + "default": 503 + } + }, + "required": [ + "address" + ] + }, + "reload": { + "description": "Automatically detect PHP file changes and reload connected services", + "type": "object", + "properties": { + "interval": { + "description": "Sync interval", + "$ref": "#/definitions/Duration", + "default": "1s" + }, + "patterns": { + "description": "Global patterns to sync", + "type": "array", + "items": { + "type": "string", + "examples": [ + ".php", + ".json" + ] + }, + "default": [ + ".php" + ] + }, + "services": { + "description": "List of included for sync services (this is a map, where key name is a plugin name)", + "type": "object", + "minProperties": 0, + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "type": "object", + "properties": { + "dirs": { + "description": "Directories to sync. If recursive is set to true, recursive sync will be applied only to the directories in 'dirs' section. Dot (.) means 'current working directory'", + "type": "array", + "default": [], + "items": { + "type": "string", + "examples": [ + ".", + "/app/src" + ], + "minLength": 1 + } + }, + "recursive": { + "description": "Recursive search for file patterns to add", + "type": "boolean", + "default": false + }, + "ignore": { + "description": "Ignored folders", + "type": "array", + "default": [], + "items": { + "type": "string", + "examples": [ + "vendor", + "/app/logs" + ], + "minLength": 1 + } + }, + "patterns": { + "description": "Service specific file pattens to sync", + "type": "array", + "default": [], + "items": { + "type": "string", + "examples": [ + ".php", + ".go", + ".md" + ], + "minLength": 1 + } + } + } + } + }, + "additionalProperties": false + } + } + }, + "nats": { + "$ref": "#/definitions/NATS_J" + }, + "boltdb": { + "$ref": "#/definitions/BoltDB_J" + }, + "amqp": { + "description": "AMQP jobs driver", + "type": "object", + "properties": { + "addr": { + "description": "AMQP Uri to connect to the rabbitmq server https://www.rabbitmq.com/uri-spec.html", + "type": "string", + "default": "amqp://guest:[email protected]:5672" + } + } + }, + "beanstalk": { + "description": "Beanstalk jobs driver", + "type": "object", + "properties": { + "addr": { + "description": "Beanstalk server address", + "type": "string", + "default": "tcp://127.0.0.1:11300" + }, + "timeout": { + "description": "Beanstalk connect timeout", + "type": "string", + "$ref": "#/definitions/Duration", + "default": "30s" + } + } + }, + "sqs": { + "description": "SQS jobs driver (https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html)", + "type": "object", + "properties": { + "key": { + "description": "AccessKey ID", + "type": "string", + "default": null + }, + "secret": { + "description": "Secret Access key", + "type": "string", + "default": null + }, + "region": { + "description": "AWS Region", + "type": "string", + "default": null + }, + "session_token": { + "description": "AWS Session token", + "type": "string", + "default": null + }, + "endpoint": { + "description": "AWS SQS endpoint to connect", + "type": "string", + "default": "http://127.0.0.1:9324" + } + } + }, + "jobs": { + "description": "JOBS plugin", + "type": "object", + "properties": { + "num_pollers": { + "description": "Number of threads which will try to obtain the job from the priority queue. Default is the number of the logical CPU cores", + "type": "integer", + "examples": [ + 10, + 32 + ] + }, + "pipeline_size": { + "description": "Size of the internal priority queue, if the internal PQ reach the max number of elements, the Push operation will be blocked", + "type": "integer", + "default": 1000000 + }, + "consume": { + "description": "list of pipelines to be consumed by the server automatically at the start, keep empty if you want to start consuming manually", + "type": "array", + "items": { + "type": "string" + } + }, + "pool": { + "description": "JOBS workers pool", + "type": "object", + "$ref": "#/definitions/WorkersPool" + }, + "pipelines": { + "description": "List of broker pipelines associated with the drivers. This option is not required since you can declare pipelines in the runtime. Pipeline driver should exist.", + "type": "object", + "properties": { + "driver": { + "type": "array", + "description": "JOBS plugin driver", + "items": { + "type": "string", + "enum": [ + "amqp", + "sqs", + "beanstalk", + "boltdb", + "memory", + "nats" + ] + } + }, + "config": { + "type": "object", + "description": "driver configurations", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "type": "object", + "maxProperties": 1, + "oneOf": [ + { + "properties": { + "priority": { + "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", + "type": "integer", + "default": 10 + }, + "prefetch": { + "description": "Number of job to prefetch from the driver", + "type": "integer", + "default": 100000 + }, + "queue": { + "type": "string", + "description": "Queue name", + "default": "default" + }, + "exchange": { + "description": "Exchange name", + "type": "string", + "default": "amqp.default" + }, + "exchange_type": { + "description": "Exchange type", + "type": "string", + "default": "direct" + }, + "routing_key": { + "description": "Routing key for the queue", + "type": "string", + "default": null + }, + "exclusive": { + "description": "Declare a queue exclusive at the exchange", + "type": "boolean", + "default": false + }, + "multiple_ack": { + "description": "When multiple is true, this delivery and all prior unacknowledged deliveries on the same channel will be acknowledged. This is useful for batch processing of deliveries", + "type": "boolean", + "default": false + }, + "requeue_on_fail": { + "description": "Use rabbitmq mechanism to requeue the job on fail", + "type": "boolean", + "default": false + } + } + }, + { + "properties": { + "priority": { + "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", + "type": "integer", + "default": 10 + }, + "prefetch": { + "description": "Number of job to prefetch from the driver", + "type": "integer", + "default": 100000 + } + } + }, + { + "properties": { + "priority": { + "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", + "type": "integer", + "default": 10 + }, + "prefetch": { + "description": "Number of job to prefetch from the driver", + "type": "integer", + "default": 100000 + }, + "tube_priority": { + "description": "Beanstalk internal tube priority", + "type": "integer", + "default": 1 + }, + "tube": { + "description": "Tube name", + "type": "string", + "default": "default" + }, + "reserve_timeout": { + "description": "If no job is available before this timeout has passed, Reserve returns a ConnError recording ErrTimeout", + "$ref": "#/definitions/Duration", + "default": "5s" + } + } + }, + { + "properties": { + "priority": { + "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", + "type": "integer", + "default": 10 + }, + "prefetch": { + "description": "Number of job to prefetch from the driver", + "type": "integer", + "default": 100000 + }, + "visibility_timeout": { + "type": "integer", + "description": "The duration (in seconds) for which the call waits for a message to arrive in the queue before returning. If a message is available, the call returns sooner than WaitTimeSeconds. If no messages are available and the wait time expires, the call returns successfully with an empty list of messages.", + "default": 0 + }, + "wait_time_seconds": { + "description": "The duration (in seconds) for which the call waits for a message to arrive in the queue before returning. If a message is available, the call returns sooner than WaitTimeSeconds. If no messages are available and the wait time expires, the call returns successfully with an empty list of messages", + "type": "integer", + "default": 0 + }, + "queue": { + "description": "Queue name", + "type": "string", + "default": "default" + }, + "attributes": { + "title": "map[string]number", + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } + }, + "tags": { + "title": "map[string]string", + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + { + "properties": { + "priority": { + "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", + "type": "integer", + "default": 10 + }, + "prefetch": { + "description": "Number of job to prefetch from the driver", + "type": "integer", + "default": 100000 + }, + "subject": { + "description": "NATS subject", + "type": "string", + "default": "default" + }, + "stream": { + "description": "NATS stream", + "type": "string", + "default": "default-stream" + }, + "deliver_new": { + "description": "The consumer will only start receiving messages that were created after the consumer was created", + "type": "string", + "default": "default-stream" + }, + "rate_limit": { + "description": "Consumer rate-limiter in bytes https://docs.nats.io/jetstream/concepts/consumers#ratelimit", + "type": "integer", + "default": 1000 + }, + "delete_stream_on_stop": { + "description": "Delete the stream when after pipeline was stopped", + "type": "boolean", + "default": false + }, + "delete_after_ack": { + "description": "Delete message from the stream after successful acknowledge", + "type": "boolean", + "default": false + } + } + } + ] + } + } + } + } + } + } + }, + "tcp": { + "type": "object", + "description": "Plugin to handle RAW TCP packets, available since RR 2.6.0", + "properties": { + "servers": { + "description": "TCP servers to allocate", + "type": "object", + "minProperties": 1, + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "allOf": [ + { + "description": "User defined TCP servers", + "type": "object", + "$ref": "#/definitions/TCPServers" + } + ] + } + } + }, + "pool": { + "type": "object", + "description": "PHP static workers pool", + "$ref": "#/definitions/WorkersPool" + } + } + }, + "grpc": { + "description": "GRPC plugin", + "type": "object", + "properties": { + "listen": { + "description": "GRPC address to listen", + "type": "string", + "$ref": "#/definitions/HostAndPortWithTCP" + }, + "proto": { + "type": "array", + "description": "Proto file to use, multiply files supported [SINCE 2.6]", + "items": { + "type": "string" + } + }, + "tls": { + "description": "GRPC TLS configuration", + "type": "object", + "properties": { + "key": { + "description": "Path to the key file", + "type": "string", + "default": null + }, + "cert": { + "description": "Path to the certificate", + "type": "string", + "default": null + }, + "root_ca": { + "description": "Path to the CA certificate", + "type": "string", + "default": null + }, + "client_auth_type": { + "description": "Client auth type", + "type": "string", + "default": "no_client_certs", + "enum": [ + "request_client_cert", + "require_any_client_cert", + "verify_client_cert_if_given", + "no_client_certs" + ] + } + } + }, + "max_send_msg_size": { + "type": "integer", + "description": "Maximum send message size", + "default": 50 + }, + "max_recv_msg_size": { + "description": "Maximum receive message size", + "default": 50, + "type": "integer" + }, + "max_connection_idle": { + "description": " MaxConnectionIdle is a duration for the amount of time after which an idle connection would be closed by sending a GoAway. Idleness duration is defined since the most recent time the number of outstanding RPCs became zero or the connection establishment", + "$ref": "#/definitions/Duration" + }, + "max_connection_age": { + "description": "MaxConnectionAge is a duration for the maximum amount of time a connection may exist before it will be closed by sending a GoAway. A random jitter of +/-10% will be added to MaxConnectionAge to spread out connection storms", + "$ref": "#/definitions/Duration" + }, + "max_connection_age_grace": { + "description": "MaxConnectionAgeGrace is an additive period after MaxConnectionAge after which the connection will be forcibly closed", + "$ref": "#/definitions/Duration" + }, + "max_concurrent_streams": { + "description": "MaxConnectionAgeGrace is an additive period after MaxConnectionAge after which the connection will be forcibly closed", + "type": "integer", + "default": 10 + }, + "ping_time": { + "description": "After a duration of this time if the server doesn't see any activity it pings the client to see if the transport is still alive. If set below 1s, a minimum value of 1s will be used instead", + "$ref": "#/definitions/Duration" + }, + "timeout": { + "description": "After having pinged for keepalive check, the server waits for a duration of Timeout and if no activity is seen even after that the connection is closed", + "$ref": "#/definitions/Duration" + }, + "pool": { + "description": "GRPC workers pool", + "type": "object", + "$ref": "#/definitions/WorkersPool" + } + } + }, + "fileserver": { + "description": "[SINCE 2.6] File server to serve static files", + "type": "object", + "properties": { + "address": {}, + "calculate_etag": {}, + "weak": {}, + "stream_request_body": {}, + "serve": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "prefix": { + "description": "HTTP prefix", + "type": "string", + "examples": [ + "/foo", + "/bar/baz" + ] + }, + "root": { + "description": "Directory to serve", + "default": ".", + "type": "string" + }, + "compress": { + "description": "When set to true, the server tries minimizing CPU usage by caching compressed files", + "type": "boolean", + "default": false + }, + "cache_duration": { + "description": "Expiration duration for inactive file handlers. Units: seconds. Use negative number to disable", + "type": "integer", + "default": 10 + }, + "max_age": { + "description": "The value for the Cache-Control HTTP-header. Units: seconds", + "type": "integer", + "default": 10 + }, + "bytes_range": { + "description": "Enable range requests: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests", + "type": "boolean", + "default": false + } + }, + "required": [ + "prefix" + ] + } + } + } + } + }, + "definitions": { + "BoltDB": { + "description": "BoltDB config section", + "type": "object", + "properties": { + "file": { + "description": "file name for the db", + "type": "string", + "default": "rr.db" + }, + "permission": { + "description": "Access permission for the DB file.", + "type": "integer", + "default": "0777" + }, + "interval": { + "description": "TTL keys check interval in seconds. It's safe to use 1 second here, but can be a little costly to performance", + "type": "integer", + "default": 60 + } + } + }, + "Memcached": { + "description": "In-memory config section", + "type": "object", + "properties": { + "addr": { + "description": "Address of the memcached node", + "type": "string", + "default": "localhost:11211" + } + } + }, + "Redis": { + "description": "Redis config section", + "type": "object", + "properties": { + "addr": { + "description": "Redis server address", + "type": "array", + "default": "localhost:6379" + }, + "master_name": { + "type": "string", + "default": null + }, + "username": { + "type": "string", + "default": null + }, + "password": { + "type": "string", + "default": null + }, + "db": { + "description": "Redis db number", + "type": "integer", + "default": 0, + "maximum": 10 + }, + "sentinel_password": { + "type": "string", + "default": null + }, + "route_by_latency": { + "type": "boolean", + "default": false + }, + "route_randomly": { + "type": "boolean", + "default": false + }, + "dial_timeout": { + "description": "dial timeout", + "$ref": "#/definitions/Duration" + }, + "max_retries": { + "type": "integer", + "default": 1 + }, + "min_retry_backoff": { + "type": "integer", + "default": 0 + }, + "max_retry_backoff": { + "type": "integer", + "default": 0 + }, + "pool_size": { + "type": "integer", + "default": 0 + }, + "min_idle_conns": { + "type": "integer", + "default": 0 + }, + "max_conn_age": { + "$ref": "#/definitions/Duration" + }, + "read_timeout": { + "$ref": "#/definitions/Duration" + }, + "write_timeout": { + "$ref": "#/definitions/Duration" + }, + "pool_timeout": { + "$ref": "#/definitions/Duration" + }, + "idle_timeout": { + "$ref": "#/definitions/Duration" + }, + "idle_check_freq": { + "$ref": "#/definitions/Duration" + }, + "read_only": { + "type": "boolean", + "default": false + } + } + }, + "Memory": { + "description": "In-memory config section", + "type": "object", + "properties": { + "interval": { + "description": "TTL keys check interval in seconds. It's safe to use 1 second here, but can be a little costly to performance", + "type": "integer", + "default": 60 + } + } + }, + "Service": { + "type": "object", + "description": "User defined service", + "properties": { + "command": { + "description": "Command to execute. Can be any command here which can be executed.", + "type": "string" + }, + "env": { + "description": "Environment variables for the process", + "type": "object", + "$ref": "#/definitions/Hashmap" + }, + "process_num": { + "description": "Number of copies (processes) to start per command", + "type": "integer", + "default": 1 + }, + "exec_timeout": { + "description": "Allowed time before stop", + "type": "integer", + "$ref": "#/definitions/Duration" + }, + "remain_after_exit": { + "description": "Remain process after exit. In other words, restart process after exit with any exit code", + "type": "boolean", + "default": false + }, + "restart_sec": { + "description": "Number of seconds to wait before process restart", + "type": "integer", + "default": 30 + } + }, + "required": [ + "command" + ] + }, + "WorkersPool": { + "description": "Static pool with PHP workers", + "type": "object", + "properties": { + "debug": { + "description": "Pool debug mode. Worker will be created right before RR passes request to it", + "type": "boolean", + "default": false + }, + "num_workers": { + "description": "How many worker processes will be started. Zero (or nothing) means the number of logical CPUs", + "type": "integer", + "minimum": 0, + "default": 0 + }, + "max_jobs": { + "description": "Maximal count of worker executions. Zero (or nothing) means no limit", + "type": "integer", + "minimum": 0, + "default": 0 + }, + "allocate_timeout": { + "description": "Timeout for worker allocation. Zero means no limit", + "$ref": "#/definitions/Duration", + "default": "60s" + }, + "destroy_timeout": { + "description": "Timeout for worker destroying before process killing. Zero means no limit", + "$ref": "#/definitions/Duration", + "default": "60s" + }, + "supervisor": { + "description": "Supervisor is used to control http workers", + "type": "object", + "properties": { + "watch_tick": { + "description": "How often to check the state of the workers", + "$ref": "#/definitions/Duration", + "default": "1s" + }, + "ttl": { + "description": "Maximum time worker is allowed to live (soft limit). Zero means no limit", + "$ref": "#/definitions/Duration", + "default": "0s" + }, + "idle_ttl": { + "description": "How long worker can spend in IDLE mode after first using (soft limit). Zero means no limit", + "$ref": "#/definitions/Duration", + "default": "0s" + }, + "max_worker_memory": { + "description": "Maximal worker memory usage in megabytes (soft limit). Zero means no limit", + "type": "integer", + "minimum": 0, + "default": 0 + }, + "exec_ttl": { + "description": "Maximal job lifetime (hard limit). Zero means no limit", + "$ref": "#/definitions/Duration", + "default": "0s" + } + } + } + } + }, + "TCPServers": { + "description": "TCP server", + "type": "object", + "properties": { + "addr": { + "description": "Address to listen", + "type": "string", + "pattern": "^[0-9a-zA-Z_.-]+:[0-9]{1,5}$" + }, + "delimiter": { + "description": "Data packets delimiter. Every send should end either with EOF or with the delimiter", + "type": "string", + "default": "\r\n" + }, + "read_buf_size": { + "description": "Chunks that RR uses to read the data. In MB. If you expect big payloads on a TCP server, to reduce `read` syscalls, would be a good practice to use a fairly big enough buffer", + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 1 + } + }, + "required": [ + "addr" + ] + }, + "Duration": { + "description": "Time duration", + "anyOf": [ + { + "description": "Duration as a string. Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'", + "type": "string", + "examples": [ + "10h", + "1m", + "1h10m10s", + "30s", + "300ms", + "1µs", + "1us" + ], + "minLength": 2 + }, + { + "description": "Duration in nanoseconds", + "type": "integer", + "minimum": 0 + } + ] + }, + "HostAndPortWithTCP": { + "description": "Host and port with tcp:// prefix", + "type": "string", + "pattern": "^(tcp://[0-9a-zA-Z_.-]+|):[0-9]{1,5}$", + "examples": [ + "tcp://127.0.0.1:443" + ] + }, + "HostAndPort": { + "description": "Host and port", + "type": "string", + "pattern": "^([0-9a-zA-Z_.-]+|):[0-9]{1,5}$", + "examples": [ + "127.0.0.1:443", + ":8080" + ] + }, + "LogMode": { + "description": "Logging mode", + "type": "string", + "enum": [ + "development", + "production", + "raw" + ] + }, + "LogLevel": { + "description": "Logging level", + "type": "string", + "enum": [ + "debug", + "info", + "warn", + "error", + "panic" + ] + }, + "LogEncoding": { + "description": "Encoding format", + "type": "string", + "enum": [ + "console", + "json" + ] + }, + "LogOutput": { + "type": "string", + "examples": [ + "stdout", + "stderr", + "/var/log/rr_errors.log" + ] + }, + "Hashmap": { + "description": "Hashmap", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9._-]+$": { + "type": "string", + "examples": [ + "Any header value" + ] + } + }, + "additionalProperties": false + }, + "Bucket": { + "description": "Hashmap with floats", + "type": "object", + "patternProperties": { + "[+-]?([0-9]*[.])?[0-9]+": { + "type": "number", + "examples": [ + 1.1 + ] + } + }, + "additionalProperties": false + }, + "NATS_J": { + "description": "NATS jobs driver", + "type": "object", + "properties": { + "addr": { + "description": "NATS server address", + "type": "string", + "default": "demo.nats.io" + } + } + }, + "BoltDB_J": { + "description": "Boltdb jobs driver", + "type": "object", + "properties": { + "permissions": { + "type": "integer", + "default": "0777" + } + } + } + } +} diff --git a/schemas/config/readme.md b/schemas/config/readme.md new file mode 100644 index 00000000..e59a7469 --- /dev/null +++ b/schemas/config/readme.md @@ -0,0 +1,13 @@ +# Config file schemas + +These schemas describe RoadRunner configuration file and used by: + +- <https://github.com/SchemaStore/schemastore> + +Schemas naming agreement: `<version_major>.<version_minor>.schema.json`. + +## Contributing guide + +If you want to modify the existing schema - your changes **MUST** be backward compatible. If your changes break backward compatibility - you **MUST** create a new schema file with a fresh version and "register" it in a [schemas catalog][schemas_catalog]. + +[schemas_catalog]:https://github.com/SchemaStore/schemastore/blob/master/src/api/json/catalog.json diff --git a/schemas/readme.md b/schemas/readme.md new file mode 100644 index 00000000..6ec41a46 --- /dev/null +++ b/schemas/readme.md @@ -0,0 +1,15 @@ +# Schemas + +This directory contains public schemas for the most important parts of application. + +**Do not rename or remove this directory or any file or directory inside.** + +- You can validate existing config file using the following command: + + ```bash + $ docker run --rm -v "$(pwd):/src" -w "/src" node:14-alpine sh -c \ + "npm install -g ajv-cli && \ + ajv validate --all-errors --verbose \ + -s ./schemas/config/2.0.schema.json \ + -d ./.rr*.y*ml" + ``` diff --git a/state/process/state.go b/state/process/state.go deleted file mode 100644 index d49d526f..00000000 --- a/state/process/state.go +++ /dev/null @@ -1,56 +0,0 @@ -package process - -import ( - "github.com/shirou/gopsutil/process" - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/worker" -) - -// State provides information about specific worker. -type State struct { - // Pid contains process id. - Pid int `json:"pid"` - - // Status of the worker. - Status string `json:"status"` - - // Number of worker executions. - NumJobs uint64 `json:"numExecs"` - - // Created is unix nano timestamp of worker creation time. - Created int64 `json:"created"` - - // MemoryUsage holds the information about worker memory usage in bytes. - // Values might vary for different operating systems and based on RSS. - MemoryUsage uint64 `json:"memoryUsage"` - - // CPU_Percent returns how many percent of the CPU time this process uses - CPUPercent float64 - - // Command used in the service plugin and shows a command for the particular service - Command string -} - -// WorkerProcessState creates new worker state definition. -func WorkerProcessState(w worker.BaseProcess) (*State, error) { - const op = errors.Op("worker_process_state") - p, _ := process.NewProcess(int32(w.Pid())) - i, err := p.MemoryInfo() - if err != nil { - return nil, errors.E(op, err) - } - - percent, err := p.CPUPercent() - if err != nil { - return nil, err - } - - return &State{ - CPUPercent: percent, - Pid: int(w.Pid()), - Status: w.State().String(), - NumJobs: w.State().NumExecs(), - Created: w.Created().UnixNano(), - MemoryUsage: i.RSS, - }, nil -} diff --git a/systemd/rr.service b/systemd/rr.service deleted file mode 100755 index 9b78329e..00000000 --- a/systemd/rr.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=High-performance PHP application server - -[Service] -Type=simple -ExecStart=/usr/local/bin/roadrunner serve -c <path/to/.rr.yaml> -Restart=always -RestartSec=30 - -[Install] -WantedBy=default.target
\ No newline at end of file diff --git a/tests/allocate-failed.php b/tests/allocate-failed.php deleted file mode 100644 index 8514ecc0..00000000 --- a/tests/allocate-failed.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -if (file_exists('break')) { - throw new Exception('oops'); -} - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); - -while($rr->waitPayload()){ - $rr->respond(new \Spiral\RoadRunner\Payload("")); -} diff --git a/tests/broken.php b/tests/broken.php deleted file mode 100644 index 413f860f..00000000 --- a/tests/broken.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - echo undefined_function(); - $rr->respond(new RoadRunner\Payload((string)$in->body, null)); -} diff --git a/tests/client.php b/tests/client.php deleted file mode 100644 index d67f6937..00000000 --- a/tests/client.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -use Spiral\Goridge; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -list($test, $goridge) = [$argv[1], $argv[2]]; - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("127.0.0.1", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - break; - - default: - die("invalid protocol selection"); -} - -require_once sprintf("%s/%s.php", __DIR__, $test); diff --git a/tests/crc_error.php b/tests/crc_error.php deleted file mode 100644 index a769fb1e..00000000 --- a/tests/crc_error.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -fwrite(STDOUT, "warning: some weird php error"); - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); - -while($rr->waitPayload()){ - sleep(3); - $rr->respond(new \Spiral\RoadRunner\Payload("")); -} diff --git a/tests/delay.php b/tests/delay.php deleted file mode 100644 index 2c8255ba..00000000 --- a/tests/delay.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - try { - usleep($in->body * 1000); - $rr->respond(new RoadRunner\Payload('')); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/echo.php b/tests/echo.php deleted file mode 100644 index 64510465..00000000 --- a/tests/echo.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - try { - $rr->respond(new RoadRunner\Payload((string)$in->body)); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/error.php b/tests/error.php deleted file mode 100644 index c77e6817..00000000 --- a/tests/error.php +++ /dev/null @@ -1,13 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - $rr->error((string)$in->body); -} diff --git a/tests/exec_ttl.php b/tests/exec_ttl.php deleted file mode 100644 index fb5c9df2..00000000 --- a/tests/exec_ttl.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); - -while($rr->waitPayload()){ - sleep(3); - $rr->respond(new \Spiral\RoadRunner\Payload("")); -} diff --git a/tests/failboot.php b/tests/failboot.php deleted file mode 100644 index d59462cd..00000000 --- a/tests/failboot.php +++ /dev/null @@ -1,3 +0,0 @@ -<?php -ini_set('display_errors', 'stderr'); -throw new Error("failboot error"); diff --git a/tests/gzip-large-file.txt b/tests/gzip-large-file.txt deleted file mode 100644 index 4c3eef8f..00000000 --- a/tests/gzip-large-file.txt +++ /dev/null @@ -1,19 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sodales consectetur fringilla. Phasellus rhoncus lectus nec diam vehicula euismod. Proin eget mollis libero, at ullamcorper ex. Proin sollicitudin, ligula vitae efficitur iaculis, est arcu iaculis ex, in finibus dolor lorem sit amet orci. Pellentesque placerat fermentum est, sed aliquam nulla aliquam non. Donec non tellus vitae elit iaculis ullamcorper. Sed ultricies arcu vel nunc maximus, nec iaculis eros varius. Duis iaculis a mi sed rutrum. Aliquam aliquet ornare arcu id pulvinar. Maecenas luctus orci at efficitur mollis. Aliquam eu placerat magna, id blandit libero. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque mattis nibh in sagittis dictum. Ut eu neque mollis, pharetra nunc in, sodales neque. Aenean viverra commodo arcu, sit amet egestas magna interdum non. Sed pulvinar ornare urna quis sagittis. Duis tempor at augue sed laoreet. Curabitur aliquam at arcu a semper. Sed eu sem ante. Praesent pellentesque magna lacus, eget ultricies elit pharetra et. Sed ut nisl ultricies orci ultricies porttitor nec id dui. Etiam eu ipsum sagittis, maximus lectus non, semper justo. Fusce orci tellus, congue et consequat nec, facilisis accumsan enim. Proin facilisis quis libero a viverra. Curabitur neque magna, euismod in lacinia nec, scelerisque nec ex. - -Donec nec ullamcorper turpis. Praesent ultrices nunc non dignissim auctor. Morbi at congue lacus. Ut ut diam nec magna finibus ullamcorper. Donec auctor est ut tempor euismod. Proin tincidunt ipsum ac leo aliquet finibus. Curabitur laoreet turpis id enim rutrum, quis tincidunt sem maximus. Vivamus quis diam at lorem viverra pretium. Quisque eget diam scelerisque, hendrerit purus at, blandit lacus. Sed quis consectetur lectus, vitae molestie neque. Quisque in nisi augue. - -Proin ultricies maximus tellus, sed malesuada metus posuere at. Vestibulum a purus ut libero tincidunt dictum et vel sapien. In auctor a metus in ullamcorper. Nullam pharetra pharetra ipsum, non gravida massa tempus non. Proin et varius nunc. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque ullamcorper porttitor gravida. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In hac habitasse platea dictumst. Donec sit amet rhoncus turpis. Cras vel dui non felis consectetur varius vitae id turpis. Sed vitae hendrerit diam. Aenean neque turpis, laoreet eget libero a, pellentesque malesuada ipsum. Praesent bibendum ultrices nisi eu ornare. Nulla dignissim eu ipsum non iaculis. - -Ut eget lacus porta, posuere neque a, feugiat mi. Sed vehicula sed dui non imperdiet. Nulla quis fringilla nunc. Nunc ipsum dui, hendrerit eu diam sit amet, vehicula convallis libero. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque vel diam id felis ornare lobortis a in metus. Mauris feugiat tellus sit amet felis pellentesque ornare sit amet ac augue. Phasellus ut porta purus. Morbi vestibulum, nunc sed pellentesque rhoncus, nibh turpis varius justo, vel mollis ipsum risus at libero. Praesent eget convallis arcu. Donec placerat justo et odio dignissim interdum. Vestibulum rhoncus faucibus tempor. Quisque ipsum tortor, ullamcorper ut sagittis quis, elementum eget metus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; - -Suspendisse in mi non eros fermentum euismod a a tortor. Morbi tincidunt sagittis arcu, ut cursus urna luctus vel. Curabitur malesuada, dui at rutrum pretium, turpis sem vulputate urna, eget suscipit odio ipsum vitae enim. Nunc pellentesque nibh sed facilisis aliquam. Sed ac est sit amet dolor aliquam volutpat in ac risus. Nulla facilisi. Etiam gravida tincidunt metus vitae consectetur. Phasellus rutrum metus ut porttitor varius. Nullam sollicitudin orci ac vehicula accumsan. Maecenas vitae malesuada est. Nam viverra ante ante, id mollis justo sagittis sed. Ut facilisis, lectus id dignissim volutpat, nisl elit fringilla elit, vel facilisis neque lectus et neque. Etiam erat justo, interdum quis neque at, mattis tristique lorem. Mauris malesuada nibh ante, eget eleifend sapien condimentum efficitur. Suspendisse metus turpis, viverra vitae tortor eget, porttitor scelerisque leo. Aenean ac fermentum tortor, ut dapibus mi. - -Fusce semper velit ac tempor lobortis. Suspendisse venenatis eros est, quis sollicitudin dolor consequat vitae. Vivamus tristique erat quis eros laoreet, et dictum nibh sagittis. Integer iaculis pretium orci non venenatis. Aenean ultricies purus ac dui tempor, ut fringilla turpis feugiat. Suspendisse sodales vestibulum aliquet. Donec elit lectus, porta sit amet mollis eu, porttitor eu sem. Vivamus sit amet nisi vel leo commodo suscipit faucibus porttitor orci. Quisque ut sollicitudin quam, luctus pharetra risus. Ut tempus diam odio, elementum porttitor lacus sollicitudin non. Curabitur eget eleifend nisi. Nullam porttitor sagittis gravida. Donec sodales a lorem vel fermentum. Aliquam ac rhoncus quam. - -Fusce pulvinar imperdiet diam et sollicitudin. Quisque a urna id ex porttitor venenatis sit amet id ligula. In fringilla euismod orci vitae pulvinar. In facilisis non nibh ac rutrum. Quisque aliquam sem sit amet pharetra pellentesque. Donec imperdiet libero sed sapien consequat vehicula. Integer vitae ornare sapien. Vivamus pretium felis at urna gravida, quis luctus sem feugiat. Fusce nisl metus, dictum vitae maximus in, interdum id nunc. Phasellus sodales nibh at mi porta egestas quis nec nisl. - -Fusce vel suscipit erat, quis vulputate quam. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam a odio nulla. Suspendisse ac tincidunt erat. Duis tortor sem, blandit sed arcu vehicula, faucibus vehicula eros. Sed gravida faucibus quam sed condimentum. Donec sed imperdiet sem. Quisque purus mauris, ultrices sit amet nisl ut, luctus convallis enim. Quisque sit amet nisi ut lacus ullamcorper placerat et id dolor. Mauris tincidunt quam id posuere sodales. Integer tristique efficitur varius. Nulla facilisi. Morbi sed odio eget neque ornare congue. Duis dui massa, maximus ac ullamcorper nec, tempus ut magna. Morbi dictum nisl sagittis orci hendrerit, ac consequat odio pulvinar. Ut mollis sodales egestas. - -Maecenas ante libero, aliquam in enim quis, aliquet malesuada mauris. Quisque porta massa id sem lacinia, id blandit diam mollis. Donec rhoncus vestibulum ante. Aliquam id ligula a erat bibendum condimentum congue non eros. Praesent ac lectus convallis, laoreet quam vitae, viverra arcu. Morbi finibus ligula risus, et ornare turpis iaculis eget. Donec placerat metus vel ex mattis tristique. Donec varius dapibus leo eu pulvinar. Fusce et diam mauris. Duis consequat pharetra urna id malesuada.
\ No newline at end of file diff --git a/tests/head.php b/tests/head.php deleted file mode 100644 index 80733166..00000000 --- a/tests/head.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -while ($in = $rr->waitPayload()) { - try { - $rr->respond(new RoadRunner\Payload("", (string)$in->header)); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/http/client.php b/tests/http/client.php deleted file mode 100644 index 90b5c2b5..00000000 --- a/tests/http/client.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require dirname(__DIR__) . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -list($test, $goridge) = [$argv[1], $argv[2]]; - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("127.0.0.1", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - break; - - default: - die("invalid protocol selection"); -} - -$psr7 = new RoadRunner\Http\PSR7Worker( - new RoadRunner\Worker($relay), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -require_once sprintf("%s/%s.php", __DIR__, $test); - -while ($req = $psr7->waitRequest()) { - try { - $psr7->respond(handleRequest($req, new \Nyholm\Psr7\Response())); - } catch (\Throwable $e) { - $psr7->getWorker()->error((string)$e); - } -} diff --git a/tests/http/cookie.php b/tests/http/cookie.php deleted file mode 100644 index 97673ef5..00000000 --- a/tests/http/cookie.php +++ /dev/null @@ -1,334 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getCookieParams()['input'])); - - return $resp->withAddedHeader( - "Set-Cookie", - (new Cookie('output', 'cookie-output'))->createHeader() - ); -} - -final class Cookie -{ - /** - * The name of the cookie. - * - * @var string - */ - private $name = ''; - /** - * The value of the cookie. This value is stored on the clients computer; do not store sensitive - * information. - * - * @var string|null - */ - private $value = null; - /** - * Cookie lifetime. This value specified in seconds and declares period of time in which cookie - * will expire relatively to current time() value. - * - * @var int|null - */ - private $lifetime = null; - /** - * The path on the server in which the cookie will be available on. - * - * If set to '/', the cookie will be available within the entire domain. If set to '/foo/', - * the cookie will only be available within the /foo/ directory and all sub-directories such as - * /foo/bar/ of domain. The default value is the current directory that the cookie is being set - * in. - * - * @var string|null - */ - private $path = null; - /** - * The domain that the cookie is available. To make the cookie available on all subdomains of - * example.com then you'd set it to '.example.com'. The . is not required but makes it - * compatible with more browsers. Setting it to www.example.com will make the cookie only - * available in the www subdomain. Refer to tail matching in the spec for details. - * - * @var string|null - */ - private $domain = null; - /** - * Indicates that the cookie should only be transmitted over a secure HTTPS connection from the - * client. When set to true, the cookie will only be set if a secure connection exists. - * On the server-side, it's on the programmer to send this kind of cookie only on secure - * connection - * (e.g. with respect to $_SERVER["HTTPS"]). - * - * @var bool|null - */ - private $secure = null; - /** - * When true the cookie will be made accessible only through the HTTP protocol. This means that - * the cookie won't be accessible by scripting languages, such as JavaScript. This setting can - * effectively help to reduce identity theft through XSS attacks (although it is not supported - * by all browsers). - * - * @var bool - */ - private $httpOnly = true; - - /** - * New Cookie instance, cookies used to schedule cookie set while dispatching Response. - * - * @link http://php.net/manual/en/function.setcookie.php - * - * @param string $name The name of the cookie. - * @param string $value The value of the cookie. This value is stored on the clients - * computer; do not store sensitive information. - * @param int $lifetime Cookie lifetime. This value specified in seconds and declares period - * of time in which cookie will expire relatively to current time() - * value. - * @param string $path The path on the server in which the cookie will be available on. - * If set to '/', the cookie will be available within the entire - * domain. - * If set to '/foo/', the cookie will only be available within the - * /foo/ - * directory and all sub-directories such as /foo/bar/ of domain. The - * default value is the current directory that the cookie is being set - * in. - * @param string $domain The domain that the cookie is available. To make the cookie - * available - * on all subdomains of example.com then you'd set it to - * '.example.com'. - * The . is not required but makes it compatible with more browsers. - * Setting it to www.example.com will make the cookie only available in - * the www subdomain. Refer to tail matching in the spec for details. - * @param bool $secure Indicates that the cookie should only be transmitted over a secure - * HTTPS connection from the client. When set to true, the cookie will - * only be set if a secure connection exists. On the server-side, it's - * on the programmer to send this kind of cookie only on secure - * connection (e.g. with respect to $_SERVER["HTTPS"]). - * @param bool $httpOnly When true the cookie will be made accessible only through the HTTP - * protocol. This means that the cookie won't be accessible by - * scripting - * languages, such as JavaScript. This setting can effectively help to - * reduce identity theft through XSS attacks (although it is not - * supported by all browsers). - */ - public function __construct( - string $name, - string $value = null, - int $lifetime = null, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = true - ) { - $this->name = $name; - $this->value = $value; - $this->lifetime = $lifetime; - $this->path = $path; - $this->domain = $domain; - $this->secure = $secure; - $this->httpOnly = $httpOnly; - } - - /** - * The name of the cookie. - * - * @return string - */ - public function getName(): string - { - return $this->name; - } - - /** - * The value of the cookie. This value is stored on the clients computer; do not store sensitive - * information. - * - * @return string|null - */ - public function getValue() - { - return $this->value; - } - - /** - * The time the cookie expires. This is a Unix timestamp so is in number of seconds since the - * epoch. In other words, you'll most likely set this with the time function plus the number of - * seconds before you want it to expire. Or you might use mktime. - * - * Will return null if lifetime is not specified. - * - * @return int|null - */ - public function getExpires() - { - if ($this->lifetime === null) { - return null; - } - - return time() + $this->lifetime; - } - - /** - * The path on the server in which the cookie will be available on. - * - * If set to '/', the cookie will be available within the entire domain. If set to '/foo/', - * the cookie will only be available within the /foo/ directory and all sub-directories such as - * /foo/bar/ of domain. The default value is the current directory that the cookie is being set - * in. - * - * @return string|null - */ - public function getPath() - { - return $this->path; - } - - /** - * The domain that the cookie is available. To make the cookie available on all subdomains of - * example.com then you'd set it to '.example.com'. The . is not required but makes it - * compatible with more browsers. Setting it to www.example.com will make the cookie only - * available in the www subdomain. Refer to tail matching in the spec for details. - * - * @return string|null - */ - public function getDomain() - { - return $this->domain; - } - - /** - * Indicates that the cookie should only be transmitted over a secure HTTPS connection from the - * client. When set to true, the cookie will only be set if a secure connection exists. - * On the server-side, it's on the programmer to send this kind of cookie only on secure - * connection - * (e.g. with respect to $_SERVER["HTTPS"]). - * - * @return bool - */ - public function isSecure(): bool - { - return $this->secure; - } - - /** - * When true the cookie will be made accessible only through the HTTP protocol. This means that - * the cookie won't be accessible by scripting languages, such as JavaScript. This setting can - * effectively help to reduce identity theft through XSS attacks (although it is not supported - * by all browsers). - * - * @return bool - */ - public function isHttpOnly(): bool - { - return $this->httpOnly; - } - - /** - * Get new cookie with altered value. Original cookie object should not be changed. - * - * @param string $value - * - * @return Cookie - */ - public function withValue(string $value): self - { - $cookie = clone $this; - $cookie->value = $value; - - return $cookie; - } - - /** - * Convert cookie instance to string. - * - * @link http://www.w3.org/Protocols/rfc2109/rfc2109 - * @return string - */ - public function createHeader(): string - { - $header = [ - rawurlencode($this->name) . '=' . rawurlencode($this->value) - ]; - if ($this->lifetime !== null) { - $header[] = 'Expires=' . gmdate(\DateTime::COOKIE, $this->getExpires()); - $header[] = 'Max-Age=' . $this->lifetime; - } - if (!empty($this->path)) { - $header[] = 'Path=' . $this->path; - } - if (!empty($this->domain)) { - $header[] = 'Domain=' . $this->domain; - } - if ($this->secure) { - $header[] = 'Secure'; - } - if ($this->httpOnly) { - $header[] = 'HttpOnly'; - } - - return join('; ', $header); - } - - /** - * New Cookie instance, cookies used to schedule cookie set while dispatching Response. - * Static constructor. - * - * @link http://php.net/manual/en/function.setcookie.php - * - * @param string $name The name of the cookie. - * @param string $value The value of the cookie. This value is stored on the clients - * computer; do not store sensitive information. - * @param int $lifetime Cookie lifetime. This value specified in seconds and declares period - * of time in which cookie will expire relatively to current time() - * value. - * @param string $path The path on the server in which the cookie will be available on. - * If set to '/', the cookie will be available within the entire - * domain. - * If set to '/foo/', the cookie will only be available within the - * /foo/ - * directory and all sub-directories such as /foo/bar/ of domain. The - * default value is the current directory that the cookie is being set - * in. - * @param string $domain The domain that the cookie is available. To make the cookie - * available - * on all subdomains of example.com then you'd set it to - * '.example.com'. - * The . is not required but makes it compatible with more browsers. - * Setting it to www.example.com will make the cookie only available in - * the www subdomain. Refer to tail matching in the spec for details. - * @param bool $secure Indicates that the cookie should only be transmitted over a secure - * HTTPS connection from the client. When set to true, the cookie will - * only be set if a secure connection exists. On the server-side, it's - * on the programmer to send this kind of cookie only on secure - * connection (e.g. with respect to $_SERVER["HTTPS"]). - * @param bool $httpOnly When true the cookie will be made accessible only through the HTTP - * protocol. This means that the cookie won't be accessible by - * scripting - * languages, such as JavaScript. This setting can effectively help to - * reduce identity theft through XSS attacks (although it is not - * supported by all browsers). - * - * @return Cookie - */ - public static function create( - string $name, - string $value = null, - int $lifetime = null, - string $path = null, - string $domain = null, - bool $secure = false, - bool $httpOnly = true - ): self { - return new self($name, $value, $lifetime, $path, $domain, $secure, $httpOnly); - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->createHeader(); - } -} diff --git a/tests/http/data.php b/tests/http/data.php deleted file mode 100644 index 6570936a..00000000 --- a/tests/http/data.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $data = $req->getParsedBody(); - - ksort($data); - ksort($data['arr']); - ksort($data['arr']['x']['y']); - - $resp->getBody()->write(json_encode($data)); - - return $resp; -} diff --git a/tests/http/echo.php b/tests/http/echo.php deleted file mode 100644 index 08e29a26..00000000 --- a/tests/http/echo.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withStatus(201); -} diff --git a/tests/http/echoDelay.php b/tests/http/echoDelay.php deleted file mode 100644 index 78e85477..00000000 --- a/tests/http/echoDelay.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - sleep(1); - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withStatus(201); -} diff --git a/tests/http/echoerr.php b/tests/http/echoerr.php deleted file mode 100644 index 7e1d05e7..00000000 --- a/tests/http/echoerr.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - error_log(strtoupper($req->getQueryParams()['hello'])); - - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withStatus(201); -} diff --git a/tests/http/env.php b/tests/http/env.php deleted file mode 100644 index 3755bdea..00000000 --- a/tests/http/env.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write($_SERVER['ENV_KEY']); - return $resp; -} diff --git a/tests/http/error.php b/tests/http/error.php deleted file mode 100644 index 527e4068..00000000 --- a/tests/http/error.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - throw new Error("error"); -} diff --git a/tests/http/error2.php b/tests/http/error2.php deleted file mode 100644 index 12a672ac..00000000 --- a/tests/http/error2.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - exit(); -} diff --git a/tests/http/header.php b/tests/http/header.php deleted file mode 100644 index 393d9623..00000000 --- a/tests/http/header.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getHeaderLine('input'))); - - return $resp->withAddedHeader("Header", $req->getQueryParams()['hello']); -} diff --git a/tests/http/headers.php b/tests/http/headers.php deleted file mode 100644 index b6f3967a..00000000 --- a/tests/http/headers.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(json_encode($req->getHeaders())); - - return $resp; -} diff --git a/tests/http/ip.php b/tests/http/ip.php deleted file mode 100644 index 49eb9285..00000000 --- a/tests/http/ip.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write($req->getServerParams()['REMOTE_ADDR']); - - return $resp; -} diff --git a/tests/http/memleak.php b/tests/http/memleak.php deleted file mode 100644 index 197a7fb1..00000000 --- a/tests/http/memleak.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $mem = ''; - $mem .= str_repeat(" ", 1024*1024); - return $resp; -} diff --git a/tests/http/payload.php b/tests/http/payload.php deleted file mode 100644 index b7a0311f..00000000 --- a/tests/http/payload.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - if ($req->getHeaderLine("Content-Type") != 'application/json') { - $resp->getBody()->write("invalid content-type"); - return $resp; - } - - // we expect json body - $p = json_decode($req->getBody(), true); - $resp->getBody()->write(json_encode(array_flip($p))); - - return $resp; -} diff --git a/tests/http/pid.php b/tests/http/pid.php deleted file mode 100644 index f22d8e23..00000000 --- a/tests/http/pid.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write((string)getmypid()); - - return $resp; -} diff --git a/tests/http/push.php b/tests/http/push.php deleted file mode 100644 index d88fc076..00000000 --- a/tests/http/push.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withAddedHeader("Http2-Push", __FILE__)->withStatus(201); -} diff --git a/tests/http/request-uri.php b/tests/http/request-uri.php deleted file mode 100644 index d4c87551..00000000 --- a/tests/http/request-uri.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write($_SERVER['REQUEST_URI']); - return $resp; -} diff --git a/tests/http/server.php b/tests/http/server.php deleted file mode 100644 index 393d9623..00000000 --- a/tests/http/server.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write(strtoupper($req->getHeaderLine('input'))); - - return $resp->withAddedHeader("Header", $req->getQueryParams()['hello']); -} diff --git a/tests/http/slow-client.php b/tests/http/slow-client.php deleted file mode 100644 index 1eaa7bc8..00000000 --- a/tests/http/slow-client.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php - -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require dirname(__DIR__) . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -[$test, $goridge, $bootDelay] = [$argv[1], $argv[2], $argv[3]]; -usleep($bootDelay * 1000); - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("127.0.0.1", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - break; - - default: - die("invalid protocol selection"); -} - -$psr7 = new RoadRunner\Http\PSR7Worker( - new RoadRunner\Worker($relay), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -require_once sprintf("%s/%s.php", __DIR__, $test); - -while ($req = $psr7->waitRequest()) { - try { - $psr7->respond(handleRequest($req, new \Nyholm\Psr7\Response())); - } catch (\Throwable $e) { - $psr7->getWorker()->error((string) $e); - } -} diff --git a/tests/http/stuck.php b/tests/http/stuck.php deleted file mode 100644 index 2dea0572..00000000 --- a/tests/http/stuck.php +++ /dev/null @@ -1,11 +0,0 @@ -<?php - -use \Psr\Http\Message\ServerRequestInterface; -use \Psr\Http\Message\ResponseInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - sleep(10); - $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); - return $resp->withStatus(201); -} diff --git a/tests/http/upload.php b/tests/http/upload.php deleted file mode 100644 index 57526246..00000000 --- a/tests/http/upload.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $files = $req->getUploadedFiles(); - array_walk_recursive($files, function (&$v) { - /** - * @var \Psr\Http\Message\UploadedFileInterface $v - */ - - if ($v->getError()) { - $v = [ - 'name' => $v->getClientFilename(), - 'size' => $v->getSize(), - 'mime' => $v->getClientMediaType(), - 'error' => $v->getError(), - ]; - } else { - $v = [ - 'name' => $v->getClientFilename(), - 'size' => $v->getSize(), - 'mime' => $v->getClientMediaType(), - 'error' => $v->getError(), - 'sha512' => hash('sha512', $v->getStream()->__toString()), - ]; - } - }); - - $resp->getBody()->write(json_encode($files, JSON_UNESCAPED_SLASHES)); - - return $resp; -} diff --git a/tests/http/user-agent.php b/tests/http/user-agent.php deleted file mode 100644 index 03d7a2c8..00000000 --- a/tests/http/user-agent.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php - -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; - -function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface -{ - $resp->getBody()->write($_SERVER['HTTP_USER_AGENT']); - return $resp; -} diff --git a/tests/idle.php b/tests/idle.php deleted file mode 100644 index fb5c9df2..00000000 --- a/tests/idle.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); - -while($rr->waitPayload()){ - sleep(3); - $rr->respond(new \Spiral\RoadRunner\Payload("")); -} diff --git a/tests/issue659.php b/tests/issue659.php deleted file mode 100644 index 2a0e4f19..00000000 --- a/tests/issue659.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -$worker = new RoadRunner\Worker(new Goridge\StreamRelay(STDIN, STDOUT)); -$psr7 = new RoadRunner\Http\PSR7Worker( - $worker, - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -while ($req = $psr7->waitRequest()) { - $psr7->getWorker()->error("test_error"); -} diff --git a/tests/memleak.php b/tests/memleak.php deleted file mode 100644 index 96ed5006..00000000 --- a/tests/memleak.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); -$mem = ''; -while($rr->waitPayload()){ - $mem .= str_repeat("a", 1024*1024*10); - $rr->respond(new \Spiral\RoadRunner\Payload("")); -} diff --git a/tests/metrics-issue-571.php b/tests/metrics-issue-571.php deleted file mode 100644 index 947ae1f7..00000000 --- a/tests/metrics-issue-571.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -use Spiral\Goridge; -use Spiral\RoadRunner; -use Nyholm\Psr7\Factory; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -$worker = new RoadRunner\Http\PSR7Worker( - RoadRunner\Worker::create(), - new Factory\Psr17Factory(), - new Factory\Psr17Factory(), - new Factory\Psr17Factory() -); - -$metrics = new RoadRunner\Metrics\Metrics( - Goridge\RPC\RPC::create(RoadRunner\Environment::fromGlobals()->getRPCAddress()) -); - -$metrics->declare( - 'test', - RoadRunner\Metrics\Collector::counter()->withHelp('Test counter') -); - -while ($req = $worker->waitRequest()) { - try { - $rsp = new \Nyholm\Psr7\Response(); - $rsp->getBody()->write("hello world"); - - $metrics->add('test', 1); - - $worker->respond($rsp); - } catch (\Throwable $e) { - $worker->getWorker()->error((string)$e); - } -} diff --git a/tests/pid.php b/tests/pid.php deleted file mode 100644 index 962b609a..00000000 --- a/tests/pid.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - /** - * @var Goridge\RelayInterface $relay - */ - - use Spiral\Goridge; - use Spiral\RoadRunner; - - $rr = new RoadRunner\Worker($relay); - - while ($in = $rr->waitPayload()) { - try { - $rr->respond(new RoadRunner\Payload((string)getmypid())); - } catch (\Throwable $e) { - $rr->error((string)$e); - } - } diff --git a/tests/pipes_test_script.sh b/tests/pipes_test_script.sh deleted file mode 100755 index c759b0a6..00000000 --- a/tests/pipes_test_script.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -php ../../tests/client.php echo pipes diff --git a/tests/psr-worker-bench.php b/tests/psr-worker-bench.php deleted file mode 100644 index e809f380..00000000 --- a/tests/psr-worker-bench.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php - -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -$worker = RoadRunner\Worker::create(); -$psr7 = new RoadRunner\Http\PSR7Worker( - $worker, - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -while ($req = $psr7->waitRequest()) { - try { - $resp = new \Nyholm\Psr7\Response(); - $resp->getBody()->write("hello world"); - - $psr7->respond($resp); - } catch (\Throwable $e) { - $psr7->getWorker()->error((string)$e); - } -} diff --git a/tests/psr-worker-post.php b/tests/psr-worker-post.php deleted file mode 100644 index 2f54af5b..00000000 --- a/tests/psr-worker-post.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php - -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -$worker = RoadRunner\Worker::create(); -$psr7 = new RoadRunner\Http\PSR7Worker( - $worker, - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -while ($req = $psr7->waitRequest()) { - try { - $resp = new \Nyholm\Psr7\Response(); - $resp->getBody()->write((string) $req->getBody()); - - $psr7->respond($resp); - } catch (\Throwable $e) { - $psr7->getWorker()->error((string)$e); - } -} diff --git a/tests/psr-worker-slow.php b/tests/psr-worker-slow.php deleted file mode 100644 index 153dff68..00000000 --- a/tests/psr-worker-slow.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -$worker = new RoadRunner\Worker(new Goridge\StreamRelay(STDIN, STDOUT)); -$psr7 = new RoadRunner\Http\PSR7Worker( - $worker, - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -while ($req = $psr7->waitRequest()) { - try { - $resp = new \Nyholm\Psr7\Response(); - sleep(mt_rand(1,20)); - $resp->getBody()->write("hello world"); - - $psr7->respond($resp); - } catch (\Throwable $e) { - $psr7->getWorker()->error((string)$e); - } -} diff --git a/tests/raw-error.php b/tests/raw-error.php deleted file mode 100644 index 3caf46c5..00000000 --- a/tests/raw-error.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ -use Spiral\Goridge; -use Spiral\RoadRunner; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -$worker = new RoadRunner\Worker(new Goridge\StreamRelay(STDIN, STDOUT)); -$psr7 = new RoadRunner\Http\PSR7Worker( - $worker, - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory(), - new \Nyholm\Psr7\Factory\Psr17Factory() -); - -error_log('{"field": "value"}'); - -while ($req = $psr7->waitRequest()) {} diff --git a/tests/sample.txt b/tests/sample.txt deleted file mode 100644 index d64a3d96..00000000 --- a/tests/sample.txt +++ /dev/null @@ -1 +0,0 @@ -sample diff --git a/tests/sleep-ttl.php b/tests/sleep-ttl.php deleted file mode 100644 index 2230e615..00000000 --- a/tests/sleep-ttl.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); - -while($rr->waitPayload()){ - sleep(10); - $rr->respond(new \Spiral\RoadRunner\Payload("hello world")); -} diff --git a/tests/sleep.php b/tests/sleep.php deleted file mode 100644 index d36ae3e3..00000000 --- a/tests/sleep.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); - -while($rr->waitPayload()){ - sleep(300); - $rr->respond(new \Spiral\RoadRunner\Payload("")); -} diff --git a/tests/slow-client.php b/tests/slow-client.php deleted file mode 100644 index c21b45d2..00000000 --- a/tests/slow-client.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -use Spiral\Goridge; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -list($test, $goridge, $bootDelay, $shutdownDelay) = [$argv[1], $argv[2], $argv[3], $argv[4]]; - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("127.0.0.1", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - - break; - - default: - die("invalid protocol selection"); -} - -usleep($bootDelay * 1000); -require_once sprintf("%s/%s.php", __DIR__, $test); -usleep($shutdownDelay * 1000); diff --git a/tests/slow-destroy.php b/tests/slow-destroy.php deleted file mode 100644 index 2edbc0db..00000000 --- a/tests/slow-destroy.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php - -use Spiral\Goridge; - -ini_set('display_errors', 'stderr'); -require __DIR__ . "/vendor/autoload.php"; - -if (count($argv) < 3) { - die("need 2 arguments"); -} - -list($test, $goridge) = [$argv[1], $argv[2]]; - -switch ($goridge) { - case "pipes": - $relay = new Goridge\StreamRelay(STDIN, STDOUT); - break; - - case "tcp": - $relay = new Goridge\SocketRelay("127.0.0.1", 9007); - break; - - case "unix": - $relay = new Goridge\SocketRelay( - "sock.unix", - null, - Goridge\SocketRelay::SOCK_UNIX - ); - break; - - default: - die("invalid protocol selection"); -} - -require_once sprintf("%s/%s.php", __DIR__, $test); - -sleep(10); diff --git a/tests/slow-pid.php b/tests/slow-pid.php deleted file mode 100644 index 82785610..00000000 --- a/tests/slow-pid.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - /** - * @var Goridge\RelayInterface $relay - */ - - use Spiral\Goridge; - use Spiral\RoadRunner; - - $rr = new RoadRunner\Worker($relay); - - while ($in = $rr->waitPayload()) { - try { - sleep(1); - $rr->respond(new RoadRunner\Payload((string)getmypid())); - } catch (\Throwable $e) { - $rr->error((string)$e); - } - } diff --git a/tests/socket_test_script.sh b/tests/socket_test_script.sh deleted file mode 100755 index 3948c4fb..00000000 --- a/tests/socket_test_script.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -php ../../tests/client.php echo tcp diff --git a/tests/src/Activity/SimpleActivity.php b/tests/src/Activity/SimpleActivity.php deleted file mode 100644 index 576b126e..00000000 --- a/tests/src/Activity/SimpleActivity.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -namespace Temporal\Tests\Activity; - -use Temporal\Activity\ActivityInterface; -use Temporal\Activity\ActivityMethod; -use Temporal\Api\Common\V1\WorkflowExecution; -use Temporal\DataConverter\Bytes; -use Temporal\Tests\DTO\Message; -use Temporal\Tests\DTO\User; - -#[ActivityInterface(prefix: "SimpleActivity.")] -class SimpleActivity -{ - #[ActivityMethod] - public function echo( - string $input - ): string { - return strtoupper($input); - } - - #[ActivityMethod] - public function lower( - string $input - ): string { - return strtolower($input); - } - - #[ActivityMethod] - public function greet( - User $user - ): Message { - return new Message(sprintf("Hello %s <%s>", $user->name, $user->email)); - } - - #[ActivityMethod] - public function slow( - string $input - ): string { - sleep(2); - - return strtolower($input); - } - - #[ActivityMethod] - public function sha512( - Bytes $input - ): string { - return hash("sha512", ($input->getData())); - } - - public function updateRunID(WorkflowExecution $e): WorkflowExecution - { - $e->setRunId('updated'); - return $e; - } - - #[ActivityMethod] - public function fail() - { - throw new \Error("failed activity"); - } -}
\ No newline at end of file diff --git a/tests/src/Client/StartNewWorkflow.php b/tests/src/Client/StartNewWorkflow.php deleted file mode 100644 index 67bc1d01..00000000 --- a/tests/src/Client/StartNewWorkflow.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php - - -namespace Temporal\Tests\Client; - -use Temporal\Client; -use Temporal\Tests\Workflow\SimpleDTOWorkflow; - -use function Symfony\Component\String\s; - -class StartNewWorkflow -{ - private $stub; - - public function __construct(Client\ClientInterface $client) - { - $this->stub = $client->newWorkflowStub(SimpleDTOWorkflow::class); - } - - public function __invoke() - { - } -} diff --git a/tests/src/Workflow/SagaWorkflow.php b/tests/src/Workflow/SagaWorkflow.php deleted file mode 100644 index e47c0203..00000000 --- a/tests/src/Workflow/SagaWorkflow.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php - -/** - * This file is part of Temporal package. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Temporal\Tests\Workflow; - -use Temporal\Activity\ActivityOptions; -use Temporal\Common\RetryOptions; -use Temporal\Tests\Activity\SimpleActivity; -use Temporal\Workflow; - -#[Workflow\WorkflowInterface] -class SagaWorkflow -{ - #[Workflow\WorkflowMethod(name: 'SagaWorkflow')] - public function run() - { - $simple = Workflow::newActivityStub( - SimpleActivity::class, - ActivityOptions::new() - ->withStartToCloseTimeout(60) - ->withRetryOptions(RetryOptions::new()->withMaximumAttempts(1)) - ); - - $saga = new Workflow\Saga(); - $saga->setParallelCompensation(true); - - try { - yield $simple->echo('test'); - $saga->addCompensation( - function () use ($simple) { - yield $simple->echo('compensate echo'); - } - ); - - yield $simple->lower('TEST'); - $saga->addCompensation( - function () use ($simple) { - yield $simple->lower('COMPENSATE LOWER'); - } - ); - - yield $simple->fail(); - } catch (\Throwable $e) { - yield $saga->compensate(); - throw $e; - } - } -} diff --git a/tests/stop.php b/tests/stop.php deleted file mode 100644 index 93263821..00000000 --- a/tests/stop.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php -/** - * @var Goridge\RelayInterface $relay - */ - -use Spiral\Goridge; -use Spiral\RoadRunner; - -$rr = new RoadRunner\Worker($relay); - -$used = false; -while ($in = $rr->waitPayload()) { - try { - if ($used) { - // kill on second attempt - $rr->stop(); - continue; - } - - $used = true; - $rr->respond(new RoadRunner\Payload((string)getmypid())); - } catch (\Throwable $e) { - $rr->error((string)$e); - } -} diff --git a/tests/supervised.php b/tests/supervised.php deleted file mode 100644 index 74f8c994..00000000 --- a/tests/supervised.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php - -declare(strict_types=1); - -use Spiral\Goridge\StreamRelay; -use Spiral\RoadRunner\Worker as RoadRunner; - -require __DIR__ . "/vendor/autoload.php"; - -$rr = new RoadRunner(new StreamRelay(\STDIN, \STDOUT)); -$mem = ''; -while($rr->waitPayload()){ - $rr->respond(new \Spiral\RoadRunner\Payload("")); -} diff --git a/utils/convert.go b/utils/convert.go deleted file mode 100644 index d96acfbb..00000000 --- a/utils/convert.go +++ /dev/null @@ -1,38 +0,0 @@ -package utils - -import ( - "reflect" - "unsafe" -) - -// AsBytes returns a slice that refers to the data backing the string s. -func AsBytes(s string) []byte { - // get the pointer to the data of the string - p := unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&s)).Data) - - var b []byte - hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b)) - hdr.Data = uintptr(p) - // we need to set the cap and len for the string to byte convert - // because string is shorter than []bytes - hdr.Cap = len(s) - hdr.Len = len(s) - - // checker to check mutable access to the data - SetChecker(b) - return b -} - -// AsString returns a string that refers to the data backing the slice s. -func AsString(b []byte) string { - p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&b)).Data) - - var s string - hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) - hdr.Data = uintptr(p) - hdr.Len = len(b) - - // checker to check mutable access to the data - SetChecker(b) - return s -} diff --git a/utils/isolate.go b/utils/isolate.go deleted file mode 100755 index 202f538c..00000000 --- a/utils/isolate.go +++ /dev/null @@ -1,60 +0,0 @@ -//go:build !windows -// +build !windows - -package utils - -import ( - "fmt" - "os" - "os/exec" - "os/user" - "strconv" - "syscall" - - "github.com/spiral/errors" -) - -// IsolateProcess change gpid for the process to avoid bypassing signals to php processes. -func IsolateProcess(cmd *exec.Cmd) { - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true, Pgid: 0} -} - -// ExecuteFromUser may work only if run RR under root user -func ExecuteFromUser(cmd *exec.Cmd, u string) error { - const op = errors.Op("execute_from_user") - usr, err := user.Lookup(u) - if err != nil { - return errors.E(op, err) - } - - usrI32, err := strconv.ParseInt(usr.Uid, 10, 32) - if err != nil { - return errors.E(op, err) - } - - grI32, err := strconv.ParseInt(usr.Gid, 10, 32) - if err != nil { - return errors.E(op, err) - } - - // For more information: - // https://www.man7.org/linux/man-pages/man7/user_namespaces.7.html - // https://www.man7.org/linux/man-pages/man7/namespaces.7.html - if _, err := os.Stat("/proc/self/ns/user"); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("kernel doesn't support user namespaces") - } - if os.IsPermission(err) { - return fmt.Errorf("unable to test user namespaces due to permissions") - } - - return errors.E(op, errors.Errorf("failed to stat /proc/self/ns/user: %v", err)) - } - - cmd.SysProcAttr.Credential = &syscall.Credential{ - Uid: uint32(usrI32), - Gid: uint32(grI32), - } - - return nil -} diff --git a/utils/isolate_win.go b/utils/isolate_win.go deleted file mode 100755 index 6b6d22e0..00000000 --- a/utils/isolate_win.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build windows -// +build windows - -package utils - -import ( - "os/exec" - "syscall" -) - -// IsolateProcess change gpid for the process to avoid bypassing signals to php processes. -func IsolateProcess(cmd *exec.Cmd) { - cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP} -} - -func ExecuteFromUser(cmd *exec.Cmd, u string) error { - return nil -} diff --git a/utils/network.go b/utils/network.go deleted file mode 100755 index d9269269..00000000 --- a/utils/network.go +++ /dev/null @@ -1,108 +0,0 @@ -//go:build linux || darwin || freebsd -// +build linux darwin freebsd - -package utils - -import ( - "fmt" - "net" - "os" - "strings" - "syscall" - - "github.com/spiral/tcplisten" -) - -const ( - IPV4 string = "tcp4" - IPV6 string = "tcp6" -) - -// CreateListener -// - SO_REUSEPORT. This option allows linear scaling server performance -// on multi-CPU servers. -// See https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ for details. -// -// - TCP_DEFER_ACCEPT. This option expects the server reads from the accepted -// connection before writing to them. -// -// - TCP_FASTOPEN. See https://lwn.net/Articles/508865/ for details. -// CreateListener crates socket listener based on DSN definition. -func CreateListener(address string) (net.Listener, error) { - dsn := strings.Split(address, "://") - - switch len(dsn) { - case 1: - // assume, that there is no prefix here [127.0.0.1:8000] - return createTCPListener(dsn[0]) - case 2: - // we got two part here, first part is the transport, second - address - // [tcp://127.0.0.1:8000] OR [unix:///path/to/unix.socket] OR [error://path] - // where error is wrong transport name - switch dsn[0] { - case "unix": - // check of file exist. If exist, unlink - if fileExists(dsn[1]) { - err := syscall.Unlink(dsn[1]) - if err != nil { - return nil, fmt.Errorf("error during the unlink syscall: error %w", err) - } - } - return net.Listen(dsn[0], dsn[1]) - case "tcp": - return createTCPListener(dsn[1]) - // not an tcp or unix - default: - return nil, fmt.Errorf("invalid Protocol ([tcp://]:6001, unix://file.sock), address: %s", address) - } - // wrong number of split parts - default: - return nil, fmt.Errorf("wrong number of parsed protocol parts, address: %s", address) - } -} - -func createTCPListener(addr string) (net.Listener, error) { - cfg := tcplisten.Config{ - ReusePort: true, - DeferAccept: false, - FastOpen: true, - } - - /* - Options we may have here: - 1. [::1]:8080 //ipv6 - 2. [0:0:..]:8080 //ipv6 - 3. 127.0.0.1:8080 //ipv4 - 4. :8080 //ipv4 - 5. [::]:8080 //ipv6 - */ - host, _, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - - // consider this is IPv4 - if host == "" { - return cfg.NewListener(IPV4, addr) - } - - return cfg.NewListener(netw(net.ParseIP(host)), addr) -} - -// check if we are listening on the ipv6 or ipv4 address -func netw(addr net.IP) string { - if addr.To4() == nil { - return IPV6 - } - return IPV4 -} - -// fileExists checks if a file exists and is not a directory before we -// try using it to prevent further errors. -func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} diff --git a/utils/network_windows.go b/utils/network_windows.go deleted file mode 100755 index 88e0fdb6..00000000 --- a/utils/network_windows.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build windows -// +build windows - -package utils - -import ( - "fmt" - "net" - "os" - "strings" - "syscall" -) - -// CreateListener crates socket listener based on DSN definition. -func CreateListener(address string) (net.Listener, error) { - dsn := strings.Split(address, "://") - - switch len(dsn) { - case 1: - // assume, that there is no prefix here [127.0.0.1:8000] - return createTCPListener(dsn[0]) - case 2: - // we got two part here, first part is the transport, second - address - // [tcp://127.0.0.1:8000] OR [unix:///path/to/unix.socket] OR [error://path] - // where error is wrong transport name - switch dsn[0] { - case "unix": - // check of file exist. If exist, unlink - if fileExists(dsn[1]) { - err := syscall.Unlink(dsn[1]) - if err != nil { - return nil, fmt.Errorf("error during the unlink syscall: error %v", err) - } - } - return net.Listen(dsn[0], dsn[1]) - case "tcp": - return createTCPListener(dsn[1]) - // not an tcp or unix - default: - return nil, fmt.Errorf("invalid Protocol ([tcp://]:6001, unix://file.sock), address: %s", address) - } - // wrong number of split parts - default: - return nil, fmt.Errorf("wrong number of parsed protocol parts, address: %s", address) - } -} - -func createTCPListener(addr string) (net.Listener, error) { - listener, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - return listener, nil -} - -// fileExists checks if a file exists and is not a directory before we -// try using it to prevent further errors. -func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} diff --git a/utils/race_checker.go b/utils/race_checker.go deleted file mode 100644 index cd5ed556..00000000 --- a/utils/race_checker.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build race - -package utils - -import ( - "crypto/sha512" - "fmt" - "runtime" -) - -func SetChecker(b []byte) { - if len(b) == 0 { - return - } - c := checkIfConst(b) - go c.isStillConst() - runtime.SetFinalizer(c, (*constSlice).isStillConst) -} - -type constSlice struct { - b []byte - checksum [64]byte -} - -func checkIfConst(b []byte) *constSlice { - c := &constSlice{b: b} - c.checksum = sha512.Sum512(c.b) - return c -} - -func (c *constSlice) isStillConst() { - if sha512.Sum512(c.b) != c.checksum { - panic(fmt.Sprintf("mutable access detected 0x%012x", &c.b[0])) - } -} diff --git a/utils/race_checker_unsafe.go b/utils/race_checker_unsafe.go deleted file mode 100644 index d2b622a5..00000000 --- a/utils/race_checker_unsafe.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build !race - -package utils - -func SetChecker(_ []byte) {} diff --git a/utils/to_ptr.go b/utils/to_ptr.go deleted file mode 100644 index 7c93ef46..00000000 --- a/utils/to_ptr.go +++ /dev/null @@ -1,467 +0,0 @@ -package utils - -import "time" - -// Bool returns a pointer value for the bool value passed in. -func Bool(v bool) *bool { - return &v -} - -// BoolSlice returns a slice of bool pointers from the values -// passed in. -func BoolSlice(vs []bool) []*bool { - ps := make([]*bool, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// BoolMap returns a map of bool pointers from the values -// passed in. -func BoolMap(vs map[string]bool) map[string]*bool { - ps := make(map[string]*bool, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Byte returns a pointer value for the byte value passed in. -func Byte(v byte) *byte { - return &v -} - -// ByteSlice returns a slice of byte pointers from the values -// passed in. -func ByteSlice(vs []byte) []*byte { - ps := make([]*byte, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// ByteMap returns a map of byte pointers from the values -// passed in. -func ByteMap(vs map[string]byte) map[string]*byte { - ps := make(map[string]*byte, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// String returns a pointer value for the string value passed in. -func String(v string) *string { - return &v -} - -// StringSlice returns a slice of string pointers from the values -// passed in. -func StringSlice(vs []string) []*string { - ps := make([]*string, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// StringMap returns a map of string pointers from the values -// passed in. -func StringMap(vs map[string]string) map[string]*string { - ps := make(map[string]*string, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Int returns a pointer value for the int value passed in. -func Int(v int) *int { - return &v -} - -// IntSlice returns a slice of int pointers from the values -// passed in. -func IntSlice(vs []int) []*int { - ps := make([]*int, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// IntMap returns a map of int pointers from the values -// passed in. -func IntMap(vs map[string]int) map[string]*int { - ps := make(map[string]*int, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Int8 returns a pointer value for the int8 value passed in. -func Int8(v int8) *int8 { - return &v -} - -// Int8Slice returns a slice of int8 pointers from the values -// passed in. -func Int8Slice(vs []int8) []*int8 { - ps := make([]*int8, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Int8Map returns a map of int8 pointers from the values -// passed in. -func Int8Map(vs map[string]int8) map[string]*int8 { - ps := make(map[string]*int8, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Int16 returns a pointer value for the int16 value passed in. -func Int16(v int16) *int16 { - return &v -} - -// Int16Slice returns a slice of int16 pointers from the values -// passed in. -func Int16Slice(vs []int16) []*int16 { - ps := make([]*int16, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Int16Map returns a map of int16 pointers from the values -// passed in. -func Int16Map(vs map[string]int16) map[string]*int16 { - ps := make(map[string]*int16, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Int32 returns a pointer value for the int32 value passed in. -func Int32(v int32) *int32 { - return &v -} - -// Int32Slice returns a slice of int32 pointers from the values -// passed in. -func Int32Slice(vs []int32) []*int32 { - ps := make([]*int32, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Int32Map returns a map of int32 pointers from the values -// passed in. -func Int32Map(vs map[string]int32) map[string]*int32 { - ps := make(map[string]*int32, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Int64 returns a pointer value for the int64 value passed in. -func Int64(v int64) *int64 { - return &v -} - -// Int64Slice returns a slice of int64 pointers from the values -// passed in. -func Int64Slice(vs []int64) []*int64 { - ps := make([]*int64, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Int64Map returns a map of int64 pointers from the values -// passed in. -func Int64Map(vs map[string]int64) map[string]*int64 { - ps := make(map[string]*int64, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Uint returns a pointer value for the uint value passed in. -func Uint(v uint) *uint { - return &v -} - -// UintSlice returns a slice of uint pointers from the values -// passed in. -func UintSlice(vs []uint) []*uint { - ps := make([]*uint, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// UintMap returns a map of uint pointers from the values -// passed in. -func UintMap(vs map[string]uint) map[string]*uint { - ps := make(map[string]*uint, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Uint8 returns a pointer value for the uint8 value passed in. -func Uint8(v uint8) *uint8 { - return &v -} - -// Uint8Slice returns a slice of uint8 pointers from the values -// passed in. -func Uint8Slice(vs []uint8) []*uint8 { - ps := make([]*uint8, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Uint8Map returns a map of uint8 pointers from the values -// passed in. -func Uint8Map(vs map[string]uint8) map[string]*uint8 { - ps := make(map[string]*uint8, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Uint16 returns a pointer value for the uint16 value passed in. -func Uint16(v uint16) *uint16 { - return &v -} - -// Uint16Slice returns a slice of uint16 pointers from the values -// passed in. -func Uint16Slice(vs []uint16) []*uint16 { - ps := make([]*uint16, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Uint16Map returns a map of uint16 pointers from the values -// passed in. -func Uint16Map(vs map[string]uint16) map[string]*uint16 { - ps := make(map[string]*uint16, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Uint32 returns a pointer value for the uint32 value passed in. -func Uint32(v uint32) *uint32 { - return &v -} - -// Uint32Slice returns a slice of uint32 pointers from the values -// passed in. -func Uint32Slice(vs []uint32) []*uint32 { - ps := make([]*uint32, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Uint32Map returns a map of uint32 pointers from the values -// passed in. -func Uint32Map(vs map[string]uint32) map[string]*uint32 { - ps := make(map[string]*uint32, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Uint64 returns a pointer value for the uint64 value passed in. -func Uint64(v uint64) *uint64 { - return &v -} - -// Uint64Slice returns a slice of uint64 pointers from the values -// passed in. -func Uint64Slice(vs []uint64) []*uint64 { - ps := make([]*uint64, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Uint64Map returns a map of uint64 pointers from the values -// passed in. -func Uint64Map(vs map[string]uint64) map[string]*uint64 { - ps := make(map[string]*uint64, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Float32 returns a pointer value for the float32 value passed in. -func Float32(v float32) *float32 { - return &v -} - -// Float32Slice returns a slice of float32 pointers from the values -// passed in. -func Float32Slice(vs []float32) []*float32 { - ps := make([]*float32, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Float32Map returns a map of float32 pointers from the values -// passed in. -func Float32Map(vs map[string]float32) map[string]*float32 { - ps := make(map[string]*float32, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Float64 returns a pointer value for the float64 value passed in. -func Float64(v float64) *float64 { - return &v -} - -// Float64Slice returns a slice of float64 pointers from the values -// passed in. -func Float64Slice(vs []float64) []*float64 { - ps := make([]*float64, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// Float64Map returns a map of float64 pointers from the values -// passed in. -func Float64Map(vs map[string]float64) map[string]*float64 { - ps := make(map[string]*float64, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} - -// Time returns a pointer value for the time.Time value passed in. -func Time(v time.Time) *time.Time { - return &v -} - -// TimeSlice returns a slice of time.Time pointers from the values -// passed in. -func TimeSlice(vs []time.Time) []*time.Time { - ps := make([]*time.Time, len(vs)) - for i, v := range vs { - vv := v - ps[i] = &vv - } - - return ps -} - -// TimeMap returns a map of time.Time pointers from the values -// passed in. -func TimeMap(vs map[string]time.Time) map[string]*time.Time { - ps := make(map[string]*time.Time, len(vs)) - for k, v := range vs { - vv := v - ps[k] = &vv - } - - return ps -} diff --git a/worker/interface.go b/worker/interface.go deleted file mode 100644 index 25e98f0a..00000000 --- a/worker/interface.go +++ /dev/null @@ -1,74 +0,0 @@ -package worker - -import ( - "context" - "fmt" - "time" - - "github.com/spiral/goridge/v3/pkg/relay" - "github.com/spiral/roadrunner/v2/payload" -) - -// State represents WorkerProcess status and updated time. -type State interface { - fmt.Stringer - // Value returns StateImpl value - Value() int64 - // Set sets the StateImpl - Set(value int64) - // NumExecs shows how many times WorkerProcess was invoked - NumExecs() uint64 - // IsActive returns true if WorkerProcess not Inactive or Stopped - IsActive() bool - // RegisterExec using to registering php executions - RegisterExec() - // SetLastUsed sets worker last used time - SetLastUsed(lu uint64) - // LastUsed return worker last used time - LastUsed() uint64 -} - -type BaseProcess interface { - fmt.Stringer - - // Pid returns worker pid. - Pid() int64 - - // Created returns time worker was created at. - Created() time.Time - - // State return receive-only WorkerProcess state object, state can be used to safely access - // WorkerProcess status, time when status changed and number of WorkerProcess executions. - State() State - - // Start used to run Cmd and immediately return - Start() error - - // Wait must be called once for each WorkerProcess, call will be released once WorkerProcess is - // complete and will return process error (if any), if stderr is presented it's value - // will be wrapped as WorkerError. Method will return error code if php process fails - // to find or Start the script. - Wait() error - - // Stop sends soft termination command to the WorkerProcess and waits for process completion. - Stop() error - - // Kill kills underlying process, make sure to call Wait() func to gather - // error log from the stderr. Does not waits for process completion! - Kill() error - - // Relay returns attached to worker goridge relay - Relay() relay.Relay - - // AttachRelay used to attach goridge relay to the worker process - AttachRelay(rl relay.Relay) -} - -type SyncWorker interface { - // BaseProcess provides basic functionality for the SyncWorker - BaseProcess - // Exec used to execute payload on the SyncWorker, there is no TIMEOUTS - Exec(rqs *payload.Payload) (*payload.Payload, error) - // ExecWithTTL used to handle Exec with TTL - ExecWithTTL(ctx context.Context, p *payload.Payload) (*payload.Payload, error) -} diff --git a/worker/state.go b/worker/state.go deleted file mode 100755 index bf152e8b..00000000 --- a/worker/state.go +++ /dev/null @@ -1,111 +0,0 @@ -package worker - -import ( - "sync/atomic" -) - -// SYNC WITH worker_watcher.GET -const ( - // StateInactive - no associated process - StateInactive int64 = iota - - // StateReady - ready for job. - StateReady - - // StateWorking - working on given payload. - StateWorking - - // StateInvalid - indicates that WorkerProcess is being disabled and will be removed. - StateInvalid - - // StateStopping - process is being softly stopped. - StateStopping - - // StateKilling - process is being forcibly stopped - StateKilling - - // StateDestroyed State of worker, when no need to allocate new one - StateDestroyed - - // StateMaxJobsReached State of worker, when it reached executions limit - StateMaxJobsReached - - // StateStopped - process has been terminated. - StateStopped - - // StateErrored - error StateImpl (can't be used). - StateErrored -) - -type StateImpl struct { - value int64 - numExecs uint64 - // to be lightweight, use UnixNano - lastUsed uint64 -} - -// NewWorkerState initializes a state for the sync.Worker -func NewWorkerState(value int64) *StateImpl { - return &StateImpl{value: value} -} - -// String returns current StateImpl as string. -func (s *StateImpl) String() string { - switch s.Value() { - case StateInactive: - return "inactive" - case StateReady: - return "ready" - case StateWorking: - return "working" - case StateInvalid: - return "invalid" - case StateStopping: - return "stopping" - case StateStopped: - return "stopped" - case StateKilling: - return "killing" - case StateErrored: - return "errored" - case StateDestroyed: - return "destroyed" - } - - return "undefined" -} - -// NumExecs returns number of registered WorkerProcess execs. -func (s *StateImpl) NumExecs() uint64 { - return atomic.LoadUint64(&s.numExecs) -} - -// Value StateImpl returns StateImpl value -func (s *StateImpl) Value() int64 { - return atomic.LoadInt64(&s.value) -} - -// IsActive returns true if WorkerProcess not Inactive or Stopped -func (s *StateImpl) IsActive() bool { - val := s.Value() - return val == StateWorking || val == StateReady -} - -// Set change StateImpl value (status) -func (s *StateImpl) Set(value int64) { - atomic.StoreInt64(&s.value, value) -} - -// RegisterExec register new execution atomically -func (s *StateImpl) RegisterExec() { - atomic.AddUint64(&s.numExecs, 1) -} - -// SetLastUsed Update last used time -func (s *StateImpl) SetLastUsed(lu uint64) { - atomic.StoreUint64(&s.lastUsed, lu) -} - -func (s *StateImpl) LastUsed() uint64 { - return atomic.LoadUint64(&s.lastUsed) -} diff --git a/worker/state_test.go b/worker/state_test.go deleted file mode 100755 index c67182d6..00000000 --- a/worker/state_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package worker - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_NewState(t *testing.T) { - st := NewWorkerState(StateErrored) - - assert.Equal(t, "errored", st.String()) - - assert.Equal(t, "inactive", NewWorkerState(StateInactive).String()) - assert.Equal(t, "ready", NewWorkerState(StateReady).String()) - assert.Equal(t, "working", NewWorkerState(StateWorking).String()) - assert.Equal(t, "stopped", NewWorkerState(StateStopped).String()) - assert.Equal(t, "undefined", NewWorkerState(1000).String()) -} - -func Test_IsActive(t *testing.T) { - assert.False(t, NewWorkerState(StateInactive).IsActive()) - assert.True(t, NewWorkerState(StateReady).IsActive()) - assert.True(t, NewWorkerState(StateWorking).IsActive()) - assert.False(t, NewWorkerState(StateStopped).IsActive()) - assert.False(t, NewWorkerState(StateErrored).IsActive()) -} diff --git a/worker/sync_worker.go b/worker/sync_worker.go deleted file mode 100755 index 6ece0ad7..00000000 --- a/worker/sync_worker.go +++ /dev/null @@ -1,297 +0,0 @@ -package worker - -import ( - "bytes" - "context" - "sync" - "time" - - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/pkg/frame" - "github.com/spiral/goridge/v3/pkg/relay" - "github.com/spiral/roadrunner/v2/payload" - "go.uber.org/multierr" -) - -// Allocator is responsible for worker allocation in the pool -type Allocator func() (SyncWorker, error) - -type SyncWorkerImpl struct { - process *Process - fPool sync.Pool - bPool sync.Pool - chPool sync.Pool -} - -// From creates SyncWorker from BaseProcess -func From(process *Process) *SyncWorkerImpl { - return &SyncWorkerImpl{ - process: process, - fPool: sync.Pool{New: func() interface{} { - return frame.NewFrame() - }}, - bPool: sync.Pool{New: func() interface{} { - return new(bytes.Buffer) - }}, - - chPool: sync.Pool{New: func() interface{} { - return make(chan wexec, 1) - }}, - } -} - -// Exec payload without TTL timeout. -func (tw *SyncWorkerImpl) Exec(p *payload.Payload) (*payload.Payload, error) { - const op = errors.Op("sync_worker_exec") - - if len(p.Body) == 0 && len(p.Context) == 0 { - return nil, errors.E(op, errors.Str("payload can not be empty")) - } - - if tw.process.State().Value() != StateReady { - return nil, errors.E(op, errors.Errorf("Process is not ready (%s)", tw.process.State().String())) - } - - // set last used time - tw.process.State().SetLastUsed(uint64(time.Now().UnixNano())) - tw.process.State().Set(StateWorking) - - rsp, err := tw.execPayload(p) - if err != nil { - // just to be more verbose - if !errors.Is(errors.SoftJob, err) { - tw.process.State().Set(StateErrored) - tw.process.State().RegisterExec() - } - return nil, errors.E(op, err) - } - - // supervisor may set state of the worker during the work - // in this case we should not re-write the worker state - if tw.process.State().Value() != StateWorking { - tw.process.State().RegisterExec() - return rsp, nil - } - - tw.process.State().Set(StateReady) - tw.process.State().RegisterExec() - - return rsp, nil -} - -type wexec struct { - payload *payload.Payload - err error -} - -// ExecWithTTL executes payload without TTL timeout. -func (tw *SyncWorkerImpl) ExecWithTTL(ctx context.Context, p *payload.Payload) (*payload.Payload, error) { - const op = errors.Op("sync_worker_exec_worker_with_timeout") - - if len(p.Body) == 0 && len(p.Context) == 0 { - return nil, errors.E(op, errors.Str("payload can not be empty")) - } - - c := tw.getCh() - defer tw.putCh(c) - - // worker was killed before it started to work (supervisor) - if tw.process.State().Value() != StateReady { - return nil, errors.E(op, errors.Errorf("Process is not ready (%s)", tw.process.State().String())) - } - // set last used time - tw.process.State().SetLastUsed(uint64(time.Now().UnixNano())) - tw.process.State().Set(StateWorking) - - go func() { - rsp, err := tw.execPayload(p) - if err != nil { - // just to be more verbose - if errors.Is(errors.SoftJob, err) == false { //nolint:gosimple - tw.process.State().Set(StateErrored) - tw.process.State().RegisterExec() - } - c <- wexec{ - err: errors.E(op, err), - } - return - } - - if tw.process.State().Value() != StateWorking { - tw.process.State().RegisterExec() - c <- wexec{ - payload: rsp, - err: nil, - } - return - } - - tw.process.State().Set(StateReady) - tw.process.State().RegisterExec() - - c <- wexec{ - payload: rsp, - err: nil, - } - }() - - select { - // exec TTL reached - case <-ctx.Done(): - err := multierr.Combine(tw.Kill()) - if err != nil { - // append timeout error - err = multierr.Append(err, errors.E(op, errors.ExecTTL)) - return nil, multierr.Append(err, ctx.Err()) - } - return nil, errors.E(op, errors.ExecTTL, ctx.Err()) - case res := <-c: - if res.err != nil { - return nil, res.err - } - return res.payload, nil - } -} - -func (tw *SyncWorkerImpl) execPayload(p *payload.Payload) (*payload.Payload, error) { - const op = errors.Op("sync_worker_exec_payload") - - // get a frame - fr := tw.getFrame() - defer tw.putFrame(fr) - - // can be 0 here - fr.WriteVersion(fr.Header(), frame.VERSION_1) - fr.WriteFlags(fr.Header(), p.Codec) - - // obtain a buffer - buf := tw.get() - - buf.Write(p.Context) - buf.Write(p.Body) - - // Context offset - fr.WriteOptions(fr.HeaderPtr(), uint32(len(p.Context))) - fr.WritePayloadLen(fr.Header(), uint32(buf.Len())) - fr.WritePayload(buf.Bytes()) - - fr.WriteCRC(fr.Header()) - - // return buffer - tw.put(buf) - - err := tw.Relay().Send(fr) - if err != nil { - return nil, errors.E(op, errors.Network, err) - } - - frameR := tw.getFrame() - defer tw.putFrame(frameR) - - err = tw.process.Relay().Receive(frameR) - if err != nil { - return nil, errors.E(op, errors.Network, err) - } - if frameR == nil { - return nil, errors.E(op, errors.Network, errors.Str("nil frame received")) - } - - flags := frameR.ReadFlags() - - if flags&frame.ERROR != byte(0) { - return nil, errors.E(op, errors.SoftJob, errors.Str(string(frameR.Payload()))) - } - - options := frameR.ReadOptions(frameR.Header()) - if len(options) != 1 { - return nil, errors.E(op, errors.Decode, errors.Str("options length should be equal 1 (body offset)")) - } - - pld := &payload.Payload{ - Codec: flags, - Body: make([]byte, len(frameR.Payload()[options[0]:])), - Context: make([]byte, len(frameR.Payload()[:options[0]])), - } - - // by copying we free frame's payload slice - // we do not hold the pointer from the smaller slice to the initial (which should be in the sync.Pool) - // https://blog.golang.org/slices-intro#TOC_6. - copy(pld.Body, frameR.Payload()[options[0]:]) - copy(pld.Context, frameR.Payload()[:options[0]]) - - return pld, nil -} - -func (tw *SyncWorkerImpl) String() string { - return tw.process.String() -} - -func (tw *SyncWorkerImpl) Pid() int64 { - return tw.process.Pid() -} - -func (tw *SyncWorkerImpl) Created() time.Time { - return tw.process.Created() -} - -func (tw *SyncWorkerImpl) State() State { - return tw.process.State() -} - -func (tw *SyncWorkerImpl) Start() error { - return tw.process.Start() -} - -func (tw *SyncWorkerImpl) Wait() error { - return tw.process.Wait() -} - -func (tw *SyncWorkerImpl) Stop() error { - return tw.process.Stop() -} - -func (tw *SyncWorkerImpl) Kill() error { - return tw.process.Kill() -} - -func (tw *SyncWorkerImpl) Relay() relay.Relay { - return tw.process.Relay() -} - -func (tw *SyncWorkerImpl) AttachRelay(rl relay.Relay) { - tw.process.AttachRelay(rl) -} - -// Private - -func (tw *SyncWorkerImpl) get() *bytes.Buffer { - return tw.bPool.Get().(*bytes.Buffer) -} - -func (tw *SyncWorkerImpl) put(b *bytes.Buffer) { - b.Reset() - tw.bPool.Put(b) -} - -func (tw *SyncWorkerImpl) getFrame() *frame.Frame { - return tw.fPool.Get().(*frame.Frame) -} - -func (tw *SyncWorkerImpl) putFrame(f *frame.Frame) { - f.Reset() - tw.fPool.Put(f) -} - -func (tw *SyncWorkerImpl) getCh() chan wexec { - return tw.chPool.Get().(chan wexec) -} - -func (tw *SyncWorkerImpl) putCh(ch chan wexec) { - // just check if the chan is not empty - select { - case <-ch: - tw.chPool.Put(ch) - default: - tw.chPool.Put(ch) - } -} diff --git a/worker/sync_worker_test.go b/worker/sync_worker_test.go deleted file mode 100755 index 288cbd45..00000000 --- a/worker/sync_worker_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package worker - -import ( - "os/exec" - "testing" - - "github.com/spiral/roadrunner/v2/payload" - "github.com/stretchr/testify/assert" -) - -func Test_NotStarted_String(t *testing.T) { - cmd := exec.Command("php", "tests/client.php", "echo", "pipes") - - w, _ := InitBaseWorker(cmd) - assert.Contains(t, w.String(), "php tests/client.php echo pipes") - assert.Contains(t, w.String(), "inactive") - assert.Contains(t, w.String(), "num_execs: 0") -} - -func Test_NotStarted_Exec(t *testing.T) { - cmd := exec.Command("php", "tests/client.php", "echo", "pipes") - - w, _ := InitBaseWorker(cmd) - - sw := From(w) - - res, err := sw.Exec(&payload.Payload{Body: []byte("hello")}) - - assert.Error(t, err) - assert.Nil(t, res) - - assert.Contains(t, err.Error(), "Process is not ready (inactive)") -} diff --git a/worker/worker.go b/worker/worker.go deleted file mode 100755 index 52bdbacb..00000000 --- a/worker/worker.go +++ /dev/null @@ -1,235 +0,0 @@ -package worker - -import ( - "fmt" - "os" - "os/exec" - "strconv" - "strings" - "time" - - "github.com/spiral/errors" - "github.com/spiral/goridge/v3/pkg/relay" - "github.com/spiral/roadrunner/v2/internal" - "go.uber.org/multierr" - "go.uber.org/zap" -) - -type Options func(p *Process) - -// Process - supervised process with api over goridge.Relay. -type Process struct { - // created indicates at what time Process has been created. - created time.Time - log *zap.Logger - - // state holds information about current Process state, - // number of Process executions, buf status change time. - // publicly this object is receive-only and protected using Mutex - // and atomic counter. - state *StateImpl - - // underlying command with associated process, command must be - // provided to Process from outside in non-started form. CmdSource - // stdErr direction will be handled by Process to aggregate error message. - cmd *exec.Cmd - - // pid of the process, points to pid of underlying process and - // can be nil while process is not started. - pid int - doneCh chan struct{} - - // communication bus with underlying process. - relay relay.Relay -} - -// InitBaseWorker creates new Process over given exec.cmd. -func InitBaseWorker(cmd *exec.Cmd, options ...Options) (*Process, error) { - if cmd.Process != nil { - return nil, fmt.Errorf("can't attach to running process") - } - - w := &Process{ - created: time.Now(), - cmd: cmd, - state: NewWorkerState(StateInactive), - doneCh: make(chan struct{}, 1), - } - - // add options - for i := 0; i < len(options); i++ { - options[i](w) - } - - if w.log == nil { - z, err := zap.NewProduction() - if err != nil { - return nil, err - } - - w.log = z - } - - // set self as stderr implementation (Writer interface) - w.cmd.Stderr = w - - return w, nil -} - -func WithLog(z *zap.Logger) Options { - return func(p *Process) { - p.log = z - } -} - -// Pid returns worker pid. -func (w *Process) Pid() int64 { - return int64(w.pid) -} - -// Created returns time worker was created at. -func (w *Process) Created() time.Time { - return w.created -} - -// State return receive-only Process state object, state can be used to safely access -// Process status, time when status changed and number of Process executions. -func (w *Process) State() State { - return w.state -} - -// AttachRelay attaches relay to the worker -func (w *Process) AttachRelay(rl relay.Relay) { - w.relay = rl -} - -// Relay returns relay attached to the worker -func (w *Process) Relay() relay.Relay { - return w.relay -} - -// String returns Process description. fmt.Stringer interface -func (w *Process) String() string { - st := w.state.String() - // we can safely compare pid to 0 - if w.pid != 0 { - st = st + ", pid:" + strconv.Itoa(w.pid) - } - - return fmt.Sprintf( - "(`%s` [%s], num_execs: %v)", - strings.Join(w.cmd.Args, " "), - st, - w.state.NumExecs(), - ) -} - -func (w *Process) Start() error { - err := w.cmd.Start() - if err != nil { - return err - } - w.pid = w.cmd.Process.Pid - return nil -} - -// Wait must be called once for each Process, call will be released once Process is -// complete and will return process error (if any), if stderr is presented it's value -// will be wrapped as WorkerError. Method will return error code if php process fails -// to find or Start the script. -func (w *Process) Wait() error { - const op = errors.Op("process_wait") - var err error - err = w.cmd.Wait() - w.doneCh <- struct{}{} - - // If worker was destroyed, just exit - if w.State().Value() == StateDestroyed { - return nil - } - - // If state is different, and err is not nil, append it to the errors - if err != nil { - w.State().Set(StateErrored) - err = multierr.Combine(err, errors.E(op, err)) - } - - // closeRelay - // at this point according to the documentation (see cmd.Wait comment) - // if worker finishes with an error, message will be written to the stderr first - // and then process.cmd.Wait return an error - err2 := w.closeRelay() - if err2 != nil { - w.State().Set(StateErrored) - return multierr.Append(err, errors.E(op, err2)) - } - - if w.cmd.ProcessState.Success() { - w.State().Set(StateStopped) - return nil - } - - return err -} - -func (w *Process) closeRelay() error { - if w.relay != nil { - err := w.relay.Close() - if err != nil { - return err - } - } - return nil -} - -// Stop sends soft termination command to the Process and waits for process completion. -func (w *Process) Stop() error { - const op = errors.Op("process_stop") - - select { - // finished - case <-w.doneCh: - return nil - default: - w.state.Set(StateStopping) - err := internal.SendControl(w.relay, &internal.StopCommand{Stop: true}) - if err != nil { - w.state.Set(StateKilling) - _ = w.cmd.Process.Signal(os.Kill) - - return errors.E(op, errors.Network, err) - } - - <-w.doneCh - w.state.Set(StateStopped) - return nil - } -} - -// Kill kills underlying process, make sure to call Wait() func to gather -// error log from the stderr. Does not wait for process completion! -func (w *Process) Kill() error { - if w.State().Value() == StateDestroyed { - err := w.cmd.Process.Signal(os.Kill) - if err != nil { - return err - } - - return nil - } - - w.state.Set(StateKilling) - err := w.cmd.Process.Signal(os.Kill) - if err != nil { - return err - } - w.state.Set(StateStopped) - return nil -} - -// Worker stderr -func (w *Process) Write(p []byte) (n int, err error) { - // unsafe to use utils.AsString - w.log.Info(string(p)) - return len(p), nil -} diff --git a/worker/worker_test.go b/worker/worker_test.go deleted file mode 100755 index 805f66b5..00000000 --- a/worker/worker_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package worker - -import ( - "os/exec" - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_OnStarted(t *testing.T) { - cmd := exec.Command("php", "tests/client.php", "broken", "pipes") - assert.Nil(t, cmd.Start()) - - w, err := InitBaseWorker(cmd) - assert.Nil(t, w) - assert.NotNil(t, err) - - assert.Equal(t, "can't attach to running process", err.Error()) -} diff --git a/worker_watcher/container/channel/vec.go b/worker_watcher/container/channel/vec.go deleted file mode 100644 index 65c2066e..00000000 --- a/worker_watcher/container/channel/vec.go +++ /dev/null @@ -1,127 +0,0 @@ -package channel - -import ( - "context" - "sync" - "sync/atomic" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/worker" -) - -type Vec struct { - sync.RWMutex - // destroy signal - destroy uint64 - // channel with the workers - workers chan worker.BaseProcess -} - -func NewVector(len uint64) *Vec { - vec := &Vec{ - destroy: 0, - workers: make(chan worker.BaseProcess, len), - } - - return vec -} - -// Push is O(1) operation -// In case of TTL and full channel O(n) worst case, where n is len of the channel -func (v *Vec) Push(w worker.BaseProcess) { - select { - case v.workers <- w: - // default select branch is only possible when dealing with TTL - // because in that case, workers in the v.workers channel can be TTL-ed and killed - // but presenting in the channel - default: - // Stop Pop operations - v.Lock() - defer v.Unlock() - - /* - we can be in the default branch by the following reasons: - 1. TTL is set with no requests during the TTL - 2. Violated Get <-> Release operation (how ??) - */ - - for i := 0; i < len(v.workers); i++ { - /* - We need to drain vector until we found a worker in the Invalid/Killing/Killed/etc states. - BUT while we are draining the vector, some worker might be reallocated and pushed into the v.workers - so, down by the code, we might have a problem when pushing the new worker to the v.workers - */ - wrk := <-v.workers - - switch wrk.State().Value() { - // good states - case worker.StateWorking, worker.StateReady: - // put the worker back - // generally, while send and receive operations are concurrent (from the channel), channel behave - // like a FIFO, but when re-sending from the same goroutine it behaves like a FILO - select { - case v.workers <- wrk: - continue - default: - // kill the worker from the channel - wrk.State().Set(worker.StateInvalid) - _ = wrk.Kill() - - continue - } - /* - Bad states are here. - */ - default: - // kill the current worker (just to be sure it's dead) - if wrk != nil { - _ = wrk.Kill() - } - // replace with the new one and return from the loop - // new worker can be ttl-ed at this moment, it's possible to replace TTL-ed worker with new TTL-ed worker - // But this case will be handled in the worker_watcher::Get - select { - case v.workers <- w: - return - // the place for the new worker was occupied before - default: - // kill the new worker and reallocate it - w.State().Set(worker.StateInvalid) - _ = w.Kill() - return - } - } - } - } -} - -func (v *Vec) Remove(_ int64) {} - -func (v *Vec) Pop(ctx context.Context) (worker.BaseProcess, error) { - if atomic.LoadUint64(&v.destroy) == 1 { - // drain channel - for { - select { - case <-v.workers: - continue - default: - return nil, errors.E(errors.WatcherStopped) - } - } - } - - // used only for the TTL-ed workers - v.RLock() - defer v.RUnlock() - - select { - case w := <-v.workers: - return w, nil - case <-ctx.Done(): - return nil, errors.E(ctx.Err(), errors.NoFreeWorkers) - } -} - -func (v *Vec) Destroy() { - atomic.StoreUint64(&v.destroy, 1) -} diff --git a/worker_watcher/container/queue/queue.go b/worker_watcher/container/queue/queue.go deleted file mode 100644 index a274a836..00000000 --- a/worker_watcher/container/queue/queue.go +++ /dev/null @@ -1,102 +0,0 @@ -package queue - -import ( - "context" - "sync" - "sync/atomic" - - "github.com/spiral/roadrunner/v2/worker" -) - -const ( - initialSize = 1 - maxInitialSize = 8 - maxInternalSliceSize = 10 -) - -type Node struct { - w []worker.BaseProcess - // LL - n *Node -} - -type Queue struct { - mu sync.Mutex - - head *Node - tail *Node - - curr uint64 - len uint64 - - sliceSize uint64 -} - -func NewQueue() *Queue { - q := &Queue{ - mu: sync.Mutex{}, - head: nil, - tail: nil, - curr: 0, - len: 0, - sliceSize: 0, - } - - return q -} - -func (q *Queue) Push(w worker.BaseProcess) { - q.mu.Lock() - - if q.head == nil { - h := newNode(initialSize) - q.head = h - q.tail = h - q.sliceSize = maxInitialSize - } else if uint64(len(q.tail.w)) >= atomic.LoadUint64(&q.sliceSize) { - n := newNode(maxInternalSliceSize) - q.tail.n = n - q.tail = n - q.sliceSize = maxInternalSliceSize - } - - q.tail.w = append(q.tail.w, w) - - atomic.AddUint64(&q.len, 1) - - q.mu.Unlock() -} - -func (q *Queue) Pop(ctx context.Context) (worker.BaseProcess, error) { - q.mu.Lock() - - if q.head == nil { - return nil, nil - } - - w := q.head.w[q.curr] - q.head.w[q.curr] = nil - atomic.AddUint64(&q.len, ^uint64(0)) - atomic.AddUint64(&q.curr, 1) - - if atomic.LoadUint64(&q.curr) >= uint64(len(q.head.w)) { - n := q.head.n - q.head.n = nil - q.head = n - q.curr = 0 - } - - q.mu.Unlock() - - return w, nil -} - -func (q *Queue) Replace(oldPid int64, newWorker worker.BaseProcess) { - -} - -func (q *Queue) Destroy() {} - -func newNode(capacity int) *Node { - return &Node{w: make([]worker.BaseProcess, 0, capacity)} -} diff --git a/worker_watcher/worker_watcher.go b/worker_watcher/worker_watcher.go deleted file mode 100755 index cfadb951..00000000 --- a/worker_watcher/worker_watcher.go +++ /dev/null @@ -1,367 +0,0 @@ -package worker_watcher //nolint:stylecheck - -import ( - "context" - "sync" - "sync/atomic" - "time" - - "github.com/spiral/errors" - "github.com/spiral/roadrunner/v2/events" - "github.com/spiral/roadrunner/v2/utils" - "github.com/spiral/roadrunner/v2/worker" - "github.com/spiral/roadrunner/v2/worker_watcher/container/channel" - "go.uber.org/zap" -) - -// Vector interface represents vector container -type Vector interface { - // Push used to put worker to the vector - Push(worker.BaseProcess) - // Pop used to get worker from the vector - Pop(ctx context.Context) (worker.BaseProcess, error) - // Remove worker with provided pid - Remove(pid int64) - // Destroy used to stop releasing the workers - Destroy() - - // TODO(rustatian) Add Replace method, and remove `Remove` method. Replace will do removal and allocation - // Replace(prevPid int64, newWorker worker.BaseProcess) -} - -type workerWatcher struct { - sync.RWMutex - container Vector - // used to control Destroy stage (that all workers are in the container) - numWorkers *uint64 - - workers []worker.BaseProcess - log *zap.Logger - - allocator worker.Allocator - allocateTimeout time.Duration -} - -// NewSyncWorkerWatcher is a constructor for the Watcher -func NewSyncWorkerWatcher(allocator worker.Allocator, log *zap.Logger, numWorkers uint64, allocateTimeout time.Duration) *workerWatcher { - return &workerWatcher{ - container: channel.NewVector(numWorkers), - - log: log, - // pass a ptr to the number of workers to avoid blocking in the TTL loop - numWorkers: utils.Uint64(numWorkers), - allocateTimeout: allocateTimeout, - workers: make([]worker.BaseProcess, 0, numWorkers), - - allocator: allocator, - } -} - -func (ww *workerWatcher) Watch(workers []worker.BaseProcess) error { - ww.Lock() - defer ww.Unlock() - for i := 0; i < len(workers); i++ { - ww.container.Push(workers[i]) - // add worker to watch slice - ww.workers = append(ww.workers, workers[i]) - - go func(swc worker.BaseProcess) { - ww.wait(swc) - }(workers[i]) - } - return nil -} - -// Take is not a thread safe operation -func (ww *workerWatcher) Take(ctx context.Context) (worker.BaseProcess, error) { - const op = errors.Op("worker_watcher_get_free_worker") - // thread safe operation - w, err := ww.container.Pop(ctx) - if err != nil { - if errors.Is(errors.WatcherStopped, err) { - return nil, errors.E(op, errors.WatcherStopped) - } - - return nil, errors.E(op, err) - } - - // fast path, worker not nil and in the ReadyState - if w.State().Value() == worker.StateReady { - return w, nil - } - - // ========================================================= - // SLOW PATH - _ = w.Kill() - // no free workers in the container or worker not in the ReadyState (TTL-ed) - // try to continuously get free one - for { - w, err = ww.container.Pop(ctx) - if err != nil { - if errors.Is(errors.WatcherStopped, err) { - return nil, errors.E(op, errors.WatcherStopped) - } - return nil, errors.E(op, err) - } - - switch w.State().Value() { - // return only workers in the Ready state - // check first - case worker.StateReady: - return w, nil - case worker.StateWorking: // how?? - ww.container.Push(w) // put it back, let worker finish the work - continue - case - // all the possible wrong states - worker.StateInactive, - worker.StateDestroyed, - worker.StateErrored, - worker.StateStopped, - worker.StateInvalid, - worker.StateKilling, - worker.StateStopping: - // worker doing no work because it in the container - // so we can safely kill it (inconsistent state) - _ = w.Kill() - // try to get new worker - continue - } - } -} - -func (ww *workerWatcher) Allocate() error { - const op = errors.Op("worker_watcher_allocate_new") - - sw, err := ww.allocator() - if err != nil { - // log incident - - // if no timeout, return error immediately - if ww.allocateTimeout == 0 { - return errors.E(op, errors.WorkerAllocate, err) - } - - // every second - allocateFreq := time.NewTicker(time.Millisecond * 1000) - - tt := time.After(ww.allocateTimeout) - for { - select { - case <-tt: - // reduce number of workers - atomic.AddUint64(ww.numWorkers, ^uint64(0)) - allocateFreq.Stop() - // timeout exceed, worker can't be allocated - return errors.E(op, errors.WorkerAllocate, err) - - case <-allocateFreq.C: - sw, err = ww.allocator() - if err != nil { - // log incident - ww.log.Error("allocate retry attempt failed", zap.String("internal_event_name", events.EventWorkerError.String()), zap.Error(err)) - continue - } - - // reallocated - allocateFreq.Stop() - goto done - } - } - } - -done: - // add worker to Wait - ww.addToWatch(sw) - - ww.Lock() - // add new worker to the workers slice (to get information about workers in parallel) - ww.workers = append(ww.workers, sw) - ww.Unlock() - - // push the worker to the container - ww.Release(sw) - return nil -} - -// Remove worker -func (ww *workerWatcher) Remove(wb worker.BaseProcess) { - ww.Lock() - defer ww.Unlock() - - // set remove state - pid := wb.Pid() - - // worker will be removed on the Get operation - for i := 0; i < len(ww.workers); i++ { - if ww.workers[i].Pid() == pid { - ww.workers = append(ww.workers[:i], ww.workers[i+1:]...) - // kill worker, just to be sure it's dead - _ = wb.Kill() - return - } - } -} - -// Release O(1) operation -func (ww *workerWatcher) Release(w worker.BaseProcess) { - switch w.State().Value() { - case worker.StateReady: - ww.container.Push(w) - default: - _ = w.Kill() - } -} - -func (ww *workerWatcher) Reset(ctx context.Context) { - // destroy container, we don't use ww mutex here, since we should be able to push worker - ww.Lock() - // do not release new workers - ww.container.Destroy() - ww.Unlock() - - tt := time.NewTicker(time.Millisecond * 10) - defer tt.Stop() - for { - select { - case <-tt.C: - ww.RLock() - // that might be one of the workers is working - if atomic.LoadUint64(ww.numWorkers) != uint64(len(ww.workers)) { - ww.RUnlock() - continue - } - ww.RUnlock() - // All container at this moment are in the container - // Pop operation is blocked, push can't be done, since it's not possible to pop - ww.Lock() - - // drain channel - _, _ = ww.container.Pop(ctx) - for i := 0; i < len(ww.workers); i++ { - ww.workers[i].State().Set(worker.StateDestroyed) - // kill the worker - _ = ww.workers[i].Kill() - } - - ww.workers = make([]worker.BaseProcess, 0, atomic.LoadUint64(ww.numWorkers)) - ww.container = channel.NewVector(atomic.LoadUint64(ww.numWorkers)) - ww.Unlock() - return - case <-ctx.Done(): - // drain channel - _, _ = ww.container.Pop(ctx) - // kill workers - ww.Lock() - for i := 0; i < len(ww.workers); i++ { - ww.workers[i].State().Set(worker.StateDestroyed) - // kill the worker - _ = ww.workers[i].Kill() - } - - ww.workers = make([]worker.BaseProcess, 0, atomic.LoadUint64(ww.numWorkers)) - ww.container = channel.NewVector(atomic.LoadUint64(ww.numWorkers)) - ww.Unlock() - } - } -} - -// Destroy all underlying container (but let them complete the task) -func (ww *workerWatcher) Destroy(ctx context.Context) { - // destroy container, we don't use ww mutex here, since we should be able to push worker - ww.Lock() - // do not release new workers - ww.container.Destroy() - ww.Unlock() - - tt := time.NewTicker(time.Millisecond * 10) - defer tt.Stop() - for { - select { - case <-tt.C: - ww.RLock() - // that might be one of the workers is working - if atomic.LoadUint64(ww.numWorkers) != uint64(len(ww.workers)) { - ww.RUnlock() - continue - } - ww.RUnlock() - // All container at this moment are in the container - // Pop operation is blocked, push can't be done, since it's not possible to pop - ww.Lock() - // drain channel - _, _ = ww.container.Pop(ctx) - for i := 0; i < len(ww.workers); i++ { - ww.workers[i].State().Set(worker.StateDestroyed) - // kill the worker - _ = ww.workers[i].Kill() - } - ww.Unlock() - return - case <-ctx.Done(): - // drain channel - _, _ = ww.container.Pop(ctx) - // kill workers - ww.Lock() - for i := 0; i < len(ww.workers); i++ { - ww.workers[i].State().Set(worker.StateDestroyed) - // kill the worker - _ = ww.workers[i].Kill() - } - ww.Unlock() - } - } -} - -// List - this is O(n) operation, and it will return copy of the actual workers -func (ww *workerWatcher) List() []worker.BaseProcess { - ww.RLock() - defer ww.RUnlock() - - if len(ww.workers) == 0 { - return nil - } - - base := make([]worker.BaseProcess, 0, len(ww.workers)) - for i := 0; i < len(ww.workers); i++ { - base = append(base, ww.workers[i]) - } - - return base -} - -func (ww *workerWatcher) wait(w worker.BaseProcess) { - const op = errors.Op("worker_watcher_wait") - err := w.Wait() - if err != nil { - ww.log.Debug("worker stopped", zap.String("internal_event_name", events.EventWorkerWaitExit.String()), zap.Error(err)) - } - - // remove worker - ww.Remove(w) - - if w.State().Value() == worker.StateDestroyed { - // worker was manually destroyed, no need to replace - ww.log.Debug("worker destroyed", zap.Int64("pid", w.Pid()), zap.String("internal_event_name", events.EventWorkerDestruct.String()), zap.Error(err)) - return - } - - // set state as stopped - w.State().Set(worker.StateStopped) - - err = ww.Allocate() - if err != nil { - ww.log.Error("failed to allocate the worker", zap.String("internal_event_name", events.EventWorkerError.String()), zap.Error(err)) - - // no workers at all, panic - if len(ww.workers) == 0 && atomic.LoadUint64(ww.numWorkers) == 0 { - panic(errors.E(op, errors.WorkerAllocate, errors.Errorf("can't allocate workers: %v, no workers in the pool", err))) - } - } -} - -func (ww *workerWatcher) addToWatch(wb worker.BaseProcess) { - go func() { - ww.wait(wb) - }() -} |