summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorValery Piashchynski <[email protected]>2021-09-16 17:12:37 +0300
committerValery Piashchynski <[email protected]>2021-09-16 17:12:37 +0300
commitf3491c089b4da77fd8d2bc942a88b6b8d117a8a5 (patch)
tree32bfffb1f24eeee7b909747cc00a6a6b9fd3ee83 /plugins
parent5d2cd55ab522d4f1e65a833f91146444465a32ac (diff)
Move plugins to a separate repository
Signed-off-by: Valery Piashchynski <[email protected]>
Diffstat (limited to 'plugins')
-rw-r--r--plugins/amqp/amqpjobs/config.go67
-rw-r--r--plugins/amqp/amqpjobs/consumer.go524
-rw-r--r--plugins/amqp/amqpjobs/item.go250
-rw-r--r--plugins/amqp/amqpjobs/listener.go25
-rw-r--r--plugins/amqp/amqpjobs/rabbit_init.go57
-rw-r--r--plugins/amqp/amqpjobs/redial.go138
-rw-r--r--plugins/amqp/plugin.go41
-rw-r--r--plugins/beanstalk/config.go53
-rw-r--r--plugins/beanstalk/connection.go223
-rw-r--r--plugins/beanstalk/consumer.go374
-rw-r--r--plugins/beanstalk/encode_test.go75
-rw-r--r--plugins/beanstalk/item.go138
-rw-r--r--plugins/beanstalk/listen.go39
-rw-r--r--plugins/beanstalk/plugin.go47
-rw-r--r--plugins/boltdb/boltjobs/config.go39
-rw-r--r--plugins/boltdb/boltjobs/consumer.go430
-rw-r--r--plugins/boltdb/boltjobs/item.go229
-rw-r--r--plugins/boltdb/boltjobs/listener.go156
-rw-r--r--plugins/boltdb/boltkv/config.go30
-rw-r--r--plugins/boltdb/boltkv/driver.go472
-rw-r--r--plugins/boltdb/doc/boltjobs.drawio1
-rw-r--r--plugins/boltdb/doc/job_lifecycle.md9
-rw-r--r--plugins/boltdb/plugin.go68
-rw-r--r--plugins/broadcast/config.go27
-rw-r--r--plugins/broadcast/doc/broadcast_arch.drawio1
-rw-r--r--plugins/broadcast/interface.go7
-rw-r--r--plugins/broadcast/plugin.go192
-rw-r--r--plugins/broadcast/rpc.go87
-rw-r--r--plugins/config/config.go10
-rw-r--r--plugins/config/interface.go29
-rwxr-xr-xplugins/config/plugin.go174
-rw-r--r--plugins/gzip/plugin.go28
-rw-r--r--plugins/headers/config.go36
-rw-r--r--plugins/headers/plugin.go127
-rw-r--r--plugins/http/attributes/attributes.go89
-rw-r--r--plugins/http/config/fcgi.go7
-rw-r--r--plugins/http/config/http.go187
-rw-r--r--plugins/http/config/http2.go28
-rw-r--r--plugins/http/config/ip.go26
-rw-r--r--plugins/http/config/ssl.go84
-rw-r--r--plugins/http/config/ssl_config_test.go116
-rw-r--r--plugins/http/config/uploads_config.go46
-rw-r--r--plugins/http/metrics.go92
-rw-r--r--plugins/http/plugin.go412
-rw-r--r--plugins/http/serve.go254
-rw-r--r--plugins/informer/interface.go34
-rw-r--r--plugins/informer/plugin.go89
-rw-r--r--plugins/informer/rpc.go59
-rw-r--r--plugins/jobs/config.go62
-rw-r--r--plugins/jobs/doc/jobs_arch.drawio1
-rw-r--r--plugins/jobs/doc/response_protocol.md54
-rw-r--r--plugins/jobs/job/job.go51
-rw-r--r--plugins/jobs/job/job_test.go18
-rw-r--r--plugins/jobs/metrics.go92
-rw-r--r--plugins/jobs/pipeline/pipeline.go98
-rw-r--r--plugins/jobs/pipeline/pipeline_test.go21
-rw-r--r--plugins/jobs/plugin.go719
-rw-r--r--plugins/jobs/protocol.go78
-rw-r--r--plugins/jobs/rpc.go160
-rw-r--r--plugins/kv/config.go6
-rw-r--r--plugins/kv/doc/kv.drawio1
-rw-r--r--plugins/kv/plugin.go159
-rw-r--r--plugins/kv/rpc.go180
-rw-r--r--plugins/logger/config.go212
-rw-r--r--plugins/logger/encoder.go66
-rw-r--r--plugins/logger/enums.go12
-rw-r--r--plugins/logger/interface.go14
-rw-r--r--plugins/logger/plugin.go86
-rw-r--r--plugins/logger/std_log_adapter.go26
-rw-r--r--plugins/logger/zap_adapter.go79
-rw-r--r--plugins/memcached/memcachedkv/config.go12
-rw-r--r--plugins/memcached/memcachedkv/driver.go254
-rw-r--r--plugins/memcached/plugin.go49
-rw-r--r--plugins/memory/memoryjobs/consumer.go296
-rw-r--r--plugins/memory/memoryjobs/item.go134
-rw-r--r--plugins/memory/memorykv/config.go14
-rw-r--r--plugins/memory/memorykv/kv.go257
-rw-r--r--plugins/memory/memorypubsub/pubsub.go92
-rw-r--r--plugins/memory/plugin.go68
-rw-r--r--plugins/metrics/config.go140
-rw-r--r--plugins/metrics/config_test.go89
-rw-r--r--plugins/metrics/doc.go1
-rw-r--r--plugins/metrics/interface.go7
-rw-r--r--plugins/metrics/plugin.go242
-rw-r--r--plugins/metrics/rpc.go294
-rw-r--r--plugins/redis/config.go34
-rw-r--r--plugins/redis/kv/config.go36
-rw-r--r--plugins/redis/kv/kv.go255
-rw-r--r--plugins/redis/plugin.go77
-rw-r--r--plugins/redis/pubsub/channel.go97
-rw-r--r--plugins/redis/pubsub/config.go34
-rw-r--r--plugins/redis/pubsub/pubsub.go187
-rw-r--r--plugins/reload/config.go62
-rw-r--r--plugins/reload/plugin.go167
-rw-r--r--plugins/reload/watcher.go372
-rw-r--r--plugins/resetter/interface.go7
-rw-r--r--plugins/resetter/plugin.go55
-rw-r--r--plugins/resetter/rpc.go29
-rw-r--r--plugins/rpc/config.go46
-rw-r--r--plugins/rpc/doc/plugin_arch.drawio1
-rw-r--r--plugins/rpc/interface.go7
-rw-r--r--plugins/rpc/plugin.go155
-rw-r--r--plugins/server/command.go33
-rw-r--r--plugins/server/command_test.go43
-rw-r--r--plugins/server/config.go60
-rw-r--r--plugins/server/interface.go23
-rw-r--r--plugins/server/plugin.go268
-rw-r--r--plugins/service/config.go34
-rw-r--r--plugins/service/plugin.go110
-rw-r--r--plugins/service/process.go147
-rw-r--r--plugins/sqs/config.go114
-rw-r--r--plugins/sqs/consumer.go421
-rw-r--r--plugins/sqs/item.go250
-rw-r--r--plugins/sqs/listener.go87
-rw-r--r--plugins/sqs/plugin.go39
-rw-r--r--plugins/static/config.go55
-rw-r--r--plugins/static/etag.go72
-rw-r--r--plugins/static/plugin.go188
-rw-r--r--plugins/status/config.go18
-rw-r--r--plugins/status/interface.go18
-rw-r--r--plugins/status/plugin.go214
-rw-r--r--plugins/status/rpc.go43
-rw-r--r--plugins/websockets/commands/enums.go9
-rw-r--r--plugins/websockets/config.go83
-rw-r--r--plugins/websockets/connection/connection.go67
-rw-r--r--plugins/websockets/doc/broadcast.drawio1
-rw-r--r--plugins/websockets/doc/doc.go27
-rw-r--r--plugins/websockets/executor/executor.go214
-rw-r--r--plugins/websockets/origin.go28
-rw-r--r--plugins/websockets/origin_test.go73
-rw-r--r--plugins/websockets/plugin.go370
-rw-r--r--plugins/websockets/pool/workers_pool.go135
-rw-r--r--plugins/websockets/validator/access_validator.go81
-rw-r--r--plugins/websockets/wildcard.go12
134 files changed, 0 insertions, 14828 deletions
diff --git a/plugins/amqp/amqpjobs/config.go b/plugins/amqp/amqpjobs/config.go
deleted file mode 100644
index ac2f6e53..00000000
--- a/plugins/amqp/amqpjobs/config.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package amqpjobs
-
-// pipeline rabbitmq info
-const (
- exchangeKey string = "exchange"
- exchangeType string = "exchange_type"
- queue string = "queue"
- routingKey string = "routing_key"
- prefetch string = "prefetch"
- exclusive string = "exclusive"
- priority string = "priority"
- multipleAsk string = "multiple_ask"
- requeueOnFail string = "requeue_on_fail"
-
- dlx string = "x-dead-letter-exchange"
- dlxRoutingKey string = "x-dead-letter-routing-key"
- dlxTTL string = "x-message-ttl"
- dlxExpires string = "x-expires"
-
- contentType string = "application/octet-stream"
-)
-
-type GlobalCfg struct {
- Addr string `mapstructure:"addr"`
-}
-
-// Config is used to parse pipeline configuration
-type Config struct {
- Prefetch int `mapstructure:"prefetch"`
- Queue string `mapstructure:"queue"`
- Priority int64 `mapstructure:"priority"`
- Exchange string `mapstructure:"exchange"`
- ExchangeType string `mapstructure:"exchange_type"`
- RoutingKey string `mapstructure:"routing_key"`
- Exclusive bool `mapstructure:"exclusive"`
- MultipleAck bool `mapstructure:"multiple_ask"`
- RequeueOnFail bool `mapstructure:"requeue_on_fail"`
-}
-
-func (c *Config) InitDefault() {
- // all options should be in sync with the pipeline defaults in the FromPipeline method
- if c.ExchangeType == "" {
- c.ExchangeType = "direct"
- }
-
- if c.Exchange == "" {
- c.Exchange = "amqp.default"
- }
-
- if c.Queue == "" {
- c.Queue = "default"
- }
-
- if c.Prefetch == 0 {
- c.Prefetch = 10
- }
-
- if c.Priority == 0 {
- c.Priority = 10
- }
-}
-
-func (c *GlobalCfg) InitDefault() {
- if c.Addr == "" {
- c.Addr = "amqp://guest:[email protected]:5672/"
- }
-}
diff --git a/plugins/amqp/amqpjobs/consumer.go b/plugins/amqp/amqpjobs/consumer.go
deleted file mode 100644
index 2ff0a40a..00000000
--- a/plugins/amqp/amqpjobs/consumer.go
+++ /dev/null
@@ -1,524 +0,0 @@
-package amqpjobs
-
-import (
- "context"
- "fmt"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/google/uuid"
- amqp "github.com/rabbitmq/amqp091-go"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- jobState "github.com/spiral/roadrunner/v2/pkg/state/job"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-const (
- pluginName string = "amqp"
-)
-
-type consumer struct {
- sync.Mutex
- log logger.Logger
- pq priorityqueue.Queue
- eh events.Handler
-
- pipeline atomic.Value
-
- // amqp connection
- conn *amqp.Connection
- consumeChan *amqp.Channel
- publishChan chan *amqp.Channel
- consumeID string
- connStr string
-
- retryTimeout time.Duration
- //
- // prefetch QoS AMQP
- //
- prefetch int
- //
- // pipeline's priority
- //
- priority int64
- exchangeName string
- queue string
- exclusive bool
- exchangeType string
- routingKey string
- multipleAck bool
- requeueOnFail bool
-
- listeners uint32
- delayed *int64
- stopCh chan struct{}
-}
-
-// NewAMQPConsumer initializes rabbitmq pipeline
-func NewAMQPConsumer(configKey string, log logger.Logger, cfg config.Configurer, e events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("new_amqp_consumer")
- // we need to obtain two parts of the amqp information here.
- // firs part - address to connect, it is located in the global section under the amqp pluginName
- // second part - queues and other pipeline information
- // if no such key - error
- if !cfg.Has(configKey) {
- return nil, errors.E(op, errors.Errorf("no configuration by provided key: %s", configKey))
- }
-
- // if no global section
- if !cfg.Has(pluginName) {
- return nil, errors.E(op, errors.Str("no global amqp configuration, global configuration should contain amqp addrs"))
- }
-
- // PARSE CONFIGURATION START -------
- var pipeCfg Config
- var globalCfg GlobalCfg
-
- err := cfg.UnmarshalKey(configKey, &pipeCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- pipeCfg.InitDefault()
-
- err = cfg.UnmarshalKey(pluginName, &globalCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- globalCfg.InitDefault()
- // PARSE CONFIGURATION END -------
-
- jb := &consumer{
- log: log,
- pq: pq,
- eh: e,
- consumeID: uuid.NewString(),
- stopCh: make(chan struct{}),
- // TODO to config
- retryTimeout: time.Minute * 5,
- priority: pipeCfg.Priority,
- delayed: utils.Int64(0),
-
- publishChan: make(chan *amqp.Channel, 1),
- routingKey: pipeCfg.RoutingKey,
- queue: pipeCfg.Queue,
- exchangeType: pipeCfg.ExchangeType,
- exchangeName: pipeCfg.Exchange,
- prefetch: pipeCfg.Prefetch,
- exclusive: pipeCfg.Exclusive,
- multipleAck: pipeCfg.MultipleAck,
- requeueOnFail: pipeCfg.RequeueOnFail,
- }
-
- jb.conn, err = amqp.Dial(globalCfg.Addr)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // save address
- jb.connStr = globalCfg.Addr
-
- err = jb.initRabbitMQ()
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- pch, err := jb.conn.Channel()
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- jb.publishChan <- pch
-
- // run redialer and requeue listener for the connection
- jb.redialer()
-
- return jb, nil
-}
-
-func FromPipeline(pipeline *pipeline.Pipeline, log logger.Logger, cfg config.Configurer, e events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("new_amqp_consumer_from_pipeline")
- // we need to obtain two parts of the amqp information here.
- // firs part - address to connect, it is located in the global section under the amqp pluginName
- // second part - queues and other pipeline information
-
- // only global section
- if !cfg.Has(pluginName) {
- return nil, errors.E(op, errors.Str("no global amqp configuration, global configuration should contain amqp addrs"))
- }
-
- // PARSE CONFIGURATION -------
- var globalCfg GlobalCfg
-
- err := cfg.UnmarshalKey(pluginName, &globalCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- globalCfg.InitDefault()
-
- // PARSE CONFIGURATION -------
-
- jb := &consumer{
- log: log,
- eh: e,
- pq: pq,
- consumeID: uuid.NewString(),
- stopCh: make(chan struct{}),
- retryTimeout: time.Minute * 5,
- delayed: utils.Int64(0),
-
- publishChan: make(chan *amqp.Channel, 1),
- routingKey: pipeline.String(routingKey, ""),
- queue: pipeline.String(queue, "default"),
- exchangeType: pipeline.String(exchangeType, "direct"),
- exchangeName: pipeline.String(exchangeKey, "amqp.default"),
- prefetch: pipeline.Int(prefetch, 10),
- priority: int64(pipeline.Int(priority, 10)),
- exclusive: pipeline.Bool(exclusive, false),
- multipleAck: pipeline.Bool(multipleAsk, false),
- requeueOnFail: pipeline.Bool(requeueOnFail, false),
- }
-
- jb.conn, err = amqp.Dial(globalCfg.Addr)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // save address
- jb.connStr = globalCfg.Addr
-
- err = jb.initRabbitMQ()
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- pch, err := jb.conn.Channel()
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- jb.publishChan <- pch
-
- // register the pipeline
- // error here is always nil
- _ = jb.Register(context.Background(), pipeline)
-
- // run redialer for the connection
- jb.redialer()
-
- return jb, nil
-}
-
-func (c *consumer) Push(ctx context.Context, job *job.Job) error {
- const op = errors.Op("rabbitmq_push")
- // check if the pipeline registered
-
- // load atomic value
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != job.Options.Pipeline {
- return errors.E(op, errors.Errorf("no such pipeline: %s, actual: %s", job.Options.Pipeline, pipe.Name()))
- }
-
- err := c.handleItem(ctx, fromJob(job))
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (c *consumer) Register(_ context.Context, p *pipeline.Pipeline) error {
- c.pipeline.Store(p)
- return nil
-}
-
-func (c *consumer) Run(_ context.Context, p *pipeline.Pipeline) error {
- start := time.Now()
- const op = errors.Op("rabbit_run")
-
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p.Name() {
- return errors.E(op, errors.Errorf("no such pipeline registered: %s", pipe.Name()))
- }
-
- // protect connection (redial)
- c.Lock()
- defer c.Unlock()
-
- var err error
- c.consumeChan, err = c.conn.Channel()
- if err != nil {
- return errors.E(op, err)
- }
-
- err = c.consumeChan.Qos(c.prefetch, 0, false)
- if err != nil {
- return errors.E(op, err)
- }
-
- // start reading messages from the channel
- deliv, err := c.consumeChan.Consume(
- c.queue,
- c.consumeID,
- false,
- false,
- false,
- false,
- nil,
- )
- if err != nil {
- return errors.E(op, err)
- }
-
- // run listener
- c.listener(deliv)
-
- atomic.StoreUint32(&c.listeners, 1)
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- return nil
-}
-
-func (c *consumer) State(ctx context.Context) (*jobState.State, error) {
- const op = errors.Op("amqp_driver_state")
- select {
- case pch := <-c.publishChan:
- defer func() {
- c.publishChan <- pch
- }()
-
- q, err := pch.QueueInspect(c.queue)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
-
- return &jobState.State{
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Queue: q.Name,
- Active: int64(q.Messages),
- Delayed: atomic.LoadInt64(c.delayed),
- Ready: ready(atomic.LoadUint32(&c.listeners)),
- }, nil
-
- case <-ctx.Done():
- return nil, errors.E(op, errors.TimeOut, ctx.Err())
- }
-}
-
-func (c *consumer) Pause(_ context.Context, p string) {
- start := time.Now()
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- c.log.Error("no such pipeline", "requested pause on: ", p)
- }
-
- l := atomic.LoadUint32(&c.listeners)
- // no active listeners
- if l == 0 {
- c.log.Warn("no active listeners, nothing to pause")
- return
- }
-
- atomic.AddUint32(&c.listeners, ^uint32(0))
-
- // protect connection (redial)
- c.Lock()
- defer c.Unlock()
-
- err := c.consumeChan.Cancel(c.consumeID, true)
- if err != nil {
- c.log.Error("cancel publish channel, forcing close", "error", err)
- errCl := c.consumeChan.Close()
- if errCl != nil {
- c.log.Error("force close failed", "error", err)
- return
- }
- return
- }
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipePaused,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (c *consumer) Resume(_ context.Context, p string) {
- start := time.Now()
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- c.log.Error("no such pipeline", "requested resume on: ", p)
- }
-
- // protect connection (redial)
- c.Lock()
- defer c.Unlock()
-
- l := atomic.LoadUint32(&c.listeners)
- // no active listeners
- if l == 1 {
- c.log.Warn("amqp listener already in the active state")
- return
- }
-
- var err error
- c.consumeChan, err = c.conn.Channel()
- if err != nil {
- c.log.Error("create channel on rabbitmq connection", "error", err)
- return
- }
-
- err = c.consumeChan.Qos(c.prefetch, 0, false)
- if err != nil {
- c.log.Error("qos set failed", "error", err)
- return
- }
-
- // start reading messages from the channel
- deliv, err := c.consumeChan.Consume(
- c.queue,
- c.consumeID,
- false,
- false,
- false,
- false,
- nil,
- )
- if err != nil {
- c.log.Error("consume operation failed", "error", err)
- return
- }
-
- // run listener
- c.listener(deliv)
-
- // increase number of listeners
- atomic.AddUint32(&c.listeners, 1)
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (c *consumer) Stop(context.Context) error {
- start := time.Now()
- c.stopCh <- struct{}{}
-
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeStopped,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- return nil
-}
-
-// handleItem
-func (c *consumer) handleItem(ctx context.Context, msg *Item) error {
- const op = errors.Op("rabbitmq_handle_item")
- select {
- case pch := <-c.publishChan:
- // return the channel back
- defer func() {
- c.publishChan <- pch
- }()
-
- // convert
- table, err := pack(msg.ID(), msg)
- if err != nil {
- return errors.E(op, err)
- }
-
- const op = errors.Op("rabbitmq_handle_item")
- // handle timeouts
- if msg.Options.DelayDuration() > 0 {
- atomic.AddInt64(c.delayed, 1)
- // TODO declare separate method for this if condition
- // TODO dlx cache channel??
- delayMs := int64(msg.Options.DelayDuration().Seconds() * 1000)
- tmpQ := fmt.Sprintf("delayed-%d.%s.%s", delayMs, c.exchangeName, c.queue)
- _, err = pch.QueueDeclare(tmpQ, true, false, false, false, amqp.Table{
- dlx: c.exchangeName,
- dlxRoutingKey: c.routingKey,
- dlxTTL: delayMs,
- dlxExpires: delayMs * 2,
- })
- if err != nil {
- atomic.AddInt64(c.delayed, ^int64(0))
- return errors.E(op, err)
- }
-
- err = pch.QueueBind(tmpQ, tmpQ, c.exchangeName, false, nil)
- if err != nil {
- atomic.AddInt64(c.delayed, ^int64(0))
- return errors.E(op, err)
- }
-
- // insert to the local, limited pipeline
- err = pch.Publish(c.exchangeName, tmpQ, false, false, amqp.Publishing{
- Headers: table,
- ContentType: contentType,
- Timestamp: time.Now(),
- DeliveryMode: amqp.Persistent,
- Body: msg.Body(),
- })
-
- if err != nil {
- atomic.AddInt64(c.delayed, ^int64(0))
- return errors.E(op, err)
- }
-
- return nil
- }
-
- // insert to the local, limited pipeline
- err = pch.Publish(c.exchangeName, c.routingKey, false, false, amqp.Publishing{
- Headers: table,
- ContentType: contentType,
- Timestamp: time.Now(),
- DeliveryMode: amqp.Persistent,
- Body: msg.Body(),
- })
-
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
- case <-ctx.Done():
- return errors.E(op, errors.TimeOut, ctx.Err())
- }
-}
-
-func ready(r uint32) bool {
- return r > 0
-}
diff --git a/plugins/amqp/amqpjobs/item.go b/plugins/amqp/amqpjobs/item.go
deleted file mode 100644
index b837ff86..00000000
--- a/plugins/amqp/amqpjobs/item.go
+++ /dev/null
@@ -1,250 +0,0 @@
-package amqpjobs
-
-import (
- "context"
- "fmt"
- "sync/atomic"
- "time"
-
- json "github.com/json-iterator/go"
- amqp "github.com/rabbitmq/amqp091-go"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-type Item struct {
- // Job contains pluginName of job broker (usually PHP class).
- Job string `json:"job"`
-
- // Ident is unique identifier of the job, should be provided from outside
- Ident string `json:"id"`
-
- // Payload is string data (usually JSON) passed to Job broker.
- Payload string `json:"payload"`
-
- // Headers with key-values pairs
- Headers map[string][]string `json:"headers"`
-
- // Options contains set of PipelineOptions specific to job execution. Can be empty.
- Options *Options `json:"options,omitempty"`
-}
-
-// Options carry information about how to handle given job.
-type Options struct {
- // Priority is job priority, default - 10
- // pointer to distinguish 0 as a priority and nil as priority not set
- Priority int64 `json:"priority"`
-
- // Pipeline manually specified pipeline.
- Pipeline string `json:"pipeline,omitempty"`
-
- // Delay defines time duration to delay execution for. Defaults to none.
- Delay int64 `json:"delay,omitempty"`
-
- // private
- // ack delegates an acknowledgement through the Acknowledger interface that the client or server has finished work on a delivery
- ack func(multiply bool) error
-
- // nack negatively acknowledge the delivery of message(s) identified by the delivery tag from either the client or server.
- // When multiple is true, nack messages up to and including delivered messages up until the delivery tag delivered on the same channel.
- // When requeue is true, request the server to deliver this message to a different consumer. If it is not possible or requeue is false, the message will be dropped or delivered to a server configured dead-letter queue.
- // This method must not be used to select or requeue messages the client wishes not to handle, rather it is to inform the server that the client is incapable of handling this message at this time
- nack func(multiply bool, requeue bool) error
-
- // requeueFn used as a pointer to the push function
- requeueFn func(context.Context, *Item) error
- // delayed jobs TODO(rustatian): figure out how to get stats from the DLX
- delayed *int64
- multipleAsk bool
- requeue bool
-}
-
-// DelayDuration returns delay duration in a form of time.Duration.
-func (o *Options) DelayDuration() time.Duration {
- return time.Second * time.Duration(o.Delay)
-}
-
-func (i *Item) ID() string {
- return i.Ident
-}
-
-func (i *Item) Priority() int64 {
- return i.Options.Priority
-}
-
-// Body packs job payload into binary payload.
-func (i *Item) Body() []byte {
- return utils.AsBytes(i.Payload)
-}
-
-// Context packs job context (job, id) into binary payload.
-// Not used in the amqp, amqp.Table used instead
-func (i *Item) Context() ([]byte, error) {
- ctx, err := json.Marshal(
- struct {
- ID string `json:"id"`
- Job string `json:"job"`
- Headers map[string][]string `json:"headers"`
- Pipeline string `json:"pipeline"`
- }{ID: i.Ident, Job: i.Job, Headers: i.Headers, Pipeline: i.Options.Pipeline},
- )
-
- if err != nil {
- return nil, err
- }
-
- return ctx, nil
-}
-
-func (i *Item) Ack() error {
- if i.Options.Delay > 0 {
- atomic.AddInt64(i.Options.delayed, ^int64(0))
- }
- return i.Options.ack(i.Options.multipleAsk)
-}
-
-func (i *Item) Nack() error {
- if i.Options.Delay > 0 {
- atomic.AddInt64(i.Options.delayed, ^int64(0))
- }
- return i.Options.nack(false, i.Options.requeue)
-}
-
-// Requeue with the provided delay, handled by the Nack
-func (i *Item) Requeue(headers map[string][]string, delay int64) error {
- if i.Options.Delay > 0 {
- atomic.AddInt64(i.Options.delayed, ^int64(0))
- }
- // overwrite the delay
- i.Options.Delay = delay
- i.Headers = headers
-
- err := i.Options.requeueFn(context.Background(), i)
- if err != nil {
- errNack := i.Options.nack(false, true)
- if errNack != nil {
- return fmt.Errorf("requeue error: %v\nack error: %v", err, errNack)
- }
-
- return err
- }
-
- // ack the job
- err = i.Options.ack(false)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// fromDelivery converts amqp.Delivery into an Item which will be pushed to the PQ
-func (c *consumer) fromDelivery(d amqp.Delivery) (*Item, error) {
- const op = errors.Op("from_delivery_convert")
- item, err := c.unpack(d)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- i := &Item{
- Job: item.Job,
- Ident: item.Ident,
- Payload: item.Payload,
- Headers: item.Headers,
- Options: item.Options,
- }
-
- item.Options.ack = d.Ack
- item.Options.nack = d.Nack
- item.Options.delayed = c.delayed
-
- // requeue func
- item.Options.requeueFn = c.handleItem
- return i, nil
-}
-
-func fromJob(job *job.Job) *Item {
- return &Item{
- Job: job.Job,
- Ident: job.Ident,
- Payload: job.Payload,
- Headers: job.Headers,
- Options: &Options{
- Priority: job.Options.Priority,
- Pipeline: job.Options.Pipeline,
- Delay: job.Options.Delay,
- },
- }
-}
-
-// pack job metadata into headers
-func pack(id string, j *Item) (amqp.Table, error) {
- headers, err := json.Marshal(j.Headers)
- if err != nil {
- return nil, err
- }
- return amqp.Table{
- job.RRID: id,
- job.RRJob: j.Job,
- job.RRPipeline: j.Options.Pipeline,
- job.RRHeaders: headers,
- job.RRDelay: j.Options.Delay,
- job.RRPriority: j.Options.Priority,
- }, nil
-}
-
-// unpack restores jobs.Options
-func (c *consumer) unpack(d amqp.Delivery) (*Item, error) {
- item := &Item{Payload: utils.AsString(d.Body), Options: &Options{
- multipleAsk: c.multipleAck,
- requeue: c.requeueOnFail,
- requeueFn: c.handleItem,
- }}
-
- if _, ok := d.Headers[job.RRID].(string); !ok {
- return nil, errors.E(errors.Errorf("missing header `%s`", job.RRID))
- }
-
- item.Ident = d.Headers[job.RRID].(string)
-
- if _, ok := d.Headers[job.RRJob].(string); !ok {
- return nil, errors.E(errors.Errorf("missing header `%s`", job.RRJob))
- }
-
- item.Job = d.Headers[job.RRJob].(string)
-
- if _, ok := d.Headers[job.RRPipeline].(string); ok {
- item.Options.Pipeline = d.Headers[job.RRPipeline].(string)
- }
-
- if h, ok := d.Headers[job.RRHeaders].([]byte); ok {
- err := json.Unmarshal(h, &item.Headers)
- if err != nil {
- return nil, err
- }
- }
-
- if t, ok := d.Headers[job.RRDelay]; ok {
- switch t.(type) {
- case int, int16, int32, int64:
- item.Options.Delay = t.(int64)
- default:
- c.log.Warn("unknown delay type", "want:", "int, int16, int32, int64", "actual", t)
- }
- }
-
- if t, ok := d.Headers[job.RRPriority]; !ok {
- // set pipe's priority
- item.Options.Priority = c.priority
- } else {
- switch t.(type) {
- case int, int16, int32, int64:
- item.Options.Priority = t.(int64)
- default:
- c.log.Warn("unknown priority type", "want:", "int, int16, int32, int64", "actual", t)
- }
- }
-
- return item, nil
-}
diff --git a/plugins/amqp/amqpjobs/listener.go b/plugins/amqp/amqpjobs/listener.go
deleted file mode 100644
index 75c61cad..00000000
--- a/plugins/amqp/amqpjobs/listener.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package amqpjobs
-
-import amqp "github.com/rabbitmq/amqp091-go"
-
-func (c *consumer) listener(deliv <-chan amqp.Delivery) {
- go func() {
- for { //nolint:gosimple
- select {
- case msg, ok := <-deliv:
- if !ok {
- c.log.Info("delivery channel closed, leaving the rabbit listener")
- return
- }
-
- d, err := c.fromDelivery(msg)
- if err != nil {
- c.log.Error("amqp delivery convert", "error", err)
- continue
- }
- // insert job into the main priority queue
- c.pq.Insert(d)
- }
- }
- }()
-}
diff --git a/plugins/amqp/amqpjobs/rabbit_init.go b/plugins/amqp/amqpjobs/rabbit_init.go
deleted file mode 100644
index fb5f6911..00000000
--- a/plugins/amqp/amqpjobs/rabbit_init.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package amqpjobs
-
-import (
- "github.com/spiral/errors"
-)
-
-func (c *consumer) initRabbitMQ() error {
- const op = errors.Op("jobs_plugin_rmq_init")
- // Channel opens a unique, concurrent server channel to process the bulk of AMQP
- // messages. Any error from methods on this receiver will render the receiver
- // invalid and a new Channel should be opened.
- channel, err := c.conn.Channel()
- if err != nil {
- return errors.E(op, err)
- }
-
- // declare an exchange (idempotent operation)
- err = channel.ExchangeDeclare(
- c.exchangeName,
- c.exchangeType,
- true,
- false,
- false,
- false,
- nil,
- )
- if err != nil {
- return errors.E(op, err)
- }
-
- // verify or declare a queue
- q, err := channel.QueueDeclare(
- c.queue,
- false,
- false,
- c.exclusive,
- false,
- nil,
- )
- if err != nil {
- return errors.E(op, err)
- }
-
- // bind queue to the exchange
- err = channel.QueueBind(
- q.Name,
- c.routingKey,
- c.exchangeName,
- false,
- nil,
- )
- if err != nil {
- return errors.E(op, err)
- }
-
- return channel.Close()
-}
diff --git a/plugins/amqp/amqpjobs/redial.go b/plugins/amqp/amqpjobs/redial.go
deleted file mode 100644
index 698a34a6..00000000
--- a/plugins/amqp/amqpjobs/redial.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package amqpjobs
-
-import (
- "time"
-
- "github.com/cenkalti/backoff/v4"
- amqp "github.com/rabbitmq/amqp091-go"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/events"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
-)
-
-// redialer used to redial to the rabbitmq in case of the connection interrupts
-func (c *consumer) redialer() { //nolint:gocognit
- go func() {
- const op = errors.Op("rabbitmq_redial")
-
- for {
- select {
- case err := <-c.conn.NotifyClose(make(chan *amqp.Error)):
- if err == nil {
- return
- }
-
- c.Lock()
-
- // trash the broken publishing channel
- <-c.publishChan
-
- t := time.Now().UTC()
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeError,
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Error: err,
- Start: time.Now().UTC(),
- })
-
- expb := backoff.NewExponentialBackOff()
- // set the retry timeout (minutes)
- expb.MaxElapsedTime = c.retryTimeout
- operation := func() error {
- c.log.Warn("rabbitmq reconnecting, caused by", "error", err)
- var dialErr error
- c.conn, dialErr = amqp.Dial(c.connStr)
- if dialErr != nil {
- return errors.E(op, dialErr)
- }
-
- c.log.Info("rabbitmq dial succeed. trying to redeclare queues and subscribers")
-
- // re-init connection
- errInit := c.initRabbitMQ()
- if errInit != nil {
- c.log.Error("rabbitmq dial", "error", errInit)
- return errInit
- }
-
- // redeclare consume channel
- var errConnCh error
- c.consumeChan, errConnCh = c.conn.Channel()
- if errConnCh != nil {
- return errors.E(op, errConnCh)
- }
-
- // redeclare publish channel
- pch, errPubCh := c.conn.Channel()
- if errPubCh != nil {
- return errors.E(op, errPubCh)
- }
-
- // start reading messages from the channel
- deliv, err := c.consumeChan.Consume(
- c.queue,
- c.consumeID,
- false,
- false,
- false,
- false,
- nil,
- )
- if err != nil {
- return errors.E(op, err)
- }
-
- // put the fresh publishing channel
- c.publishChan <- pch
- // restart listener
- c.listener(deliv)
-
- c.log.Info("queues and subscribers redeclared successfully")
-
- return nil
- }
-
- retryErr := backoff.Retry(operation, expb)
- if retryErr != nil {
- c.Unlock()
- c.log.Error("backoff failed", "error", retryErr)
- return
- }
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Start: t,
- Elapsed: time.Since(t),
- })
-
- c.Unlock()
-
- case <-c.stopCh:
- pch := <-c.publishChan
- err := pch.Close()
- if err != nil {
- c.log.Error("publish channel close", "error", err)
- }
-
- if c.consumeChan != nil {
- err = c.consumeChan.Close()
- if err != nil {
- c.log.Error("consume channel close", "error", err)
- }
- }
-
- err = c.conn.Close()
- if err != nil {
- c.log.Error("amqp connection close", "error", err)
- }
-
- return
- }
- }
- }()
-}
diff --git a/plugins/amqp/plugin.go b/plugins/amqp/plugin.go
deleted file mode 100644
index c4f5f1da..00000000
--- a/plugins/amqp/plugin.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package amqp
-
-import (
- "github.com/spiral/roadrunner/v2/common/jobs"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- "github.com/spiral/roadrunner/v2/plugins/amqp/amqpjobs"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const (
- pluginName string = "amqp"
-)
-
-type Plugin struct {
- log logger.Logger
- cfg config.Configurer
-}
-
-func (p *Plugin) Init(log logger.Logger, cfg config.Configurer) error {
- p.log = log
- p.cfg = cfg
- return nil
-}
-
-func (p *Plugin) Name() string {
- return pluginName
-}
-
-func (p *Plugin) Available() {}
-
-func (p *Plugin) JobsConstruct(configKey string, e events.Handler, pq priorityqueue.Queue) (jobs.Consumer, error) {
- return amqpjobs.NewAMQPConsumer(configKey, p.log, p.cfg, e, pq)
-}
-
-// FromPipeline constructs AMQP driver from pipeline
-func (p *Plugin) FromPipeline(pipe *pipeline.Pipeline, e events.Handler, pq priorityqueue.Queue) (jobs.Consumer, error) {
- return amqpjobs.FromPipeline(pipe, p.log, p.cfg, e, pq)
-}
diff --git a/plugins/beanstalk/config.go b/plugins/beanstalk/config.go
deleted file mode 100644
index a8069f5d..00000000
--- a/plugins/beanstalk/config.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package beanstalk
-
-import (
- "time"
-
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-const (
- tubePriority string = "tube_priority"
- tube string = "tube"
- reserveTimeout string = "reserve_timeout"
-)
-
-type GlobalCfg struct {
- Addr string `mapstructure:"addr"`
- Timeout time.Duration `mapstructure:"timeout"`
-}
-
-func (c *GlobalCfg) InitDefault() {
- if c.Addr == "" {
- c.Addr = "tcp://127.0.0.1:11300"
- }
-
- if c.Timeout == 0 {
- c.Timeout = time.Second * 30
- }
-}
-
-type Config struct {
- PipePriority int64 `mapstructure:"priority"`
- TubePriority *uint32 `mapstructure:"tube_priority"`
- Tube string `mapstructure:"tube"`
- ReserveTimeout time.Duration `mapstructure:"reserve_timeout"`
-}
-
-func (c *Config) InitDefault() {
- if c.Tube == "" {
- c.Tube = "default"
- }
-
- if c.ReserveTimeout == 0 {
- c.ReserveTimeout = time.Second * 1
- }
-
- if c.TubePriority == nil {
- c.TubePriority = utils.Uint32(0)
- }
-
- if c.PipePriority == 0 {
- c.PipePriority = 10
- }
-}
diff --git a/plugins/beanstalk/connection.go b/plugins/beanstalk/connection.go
deleted file mode 100644
index d3241b37..00000000
--- a/plugins/beanstalk/connection.go
+++ /dev/null
@@ -1,223 +0,0 @@
-package beanstalk
-
-import (
- "context"
- "net"
- "sync"
- "time"
-
- "github.com/beanstalkd/go-beanstalk"
- "github.com/cenkalti/backoff/v4"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-type ConnPool struct {
- sync.RWMutex
-
- log logger.Logger
-
- conn *beanstalk.Conn
- connT *beanstalk.Conn
- ts *beanstalk.TubeSet
- t *beanstalk.Tube
-
- network string
- address string
- tName string
- tout time.Duration
-}
-
-func NewConnPool(network, address, tName string, tout time.Duration, log logger.Logger) (*ConnPool, error) {
- connT, err := beanstalk.DialTimeout(network, address, tout)
- if err != nil {
- return nil, err
- }
-
- connTS, err := beanstalk.DialTimeout(network, address, tout)
- if err != nil {
- return nil, err
- }
-
- tube := beanstalk.NewTube(connT, tName)
- ts := beanstalk.NewTubeSet(connTS, tName)
-
- return &ConnPool{
- log: log,
- network: network,
- address: address,
- tName: tName,
- tout: tout,
- conn: connTS,
- connT: connT,
- ts: ts,
- t: tube,
- }, nil
-}
-
-// Put the payload
-// TODO use the context ??
-func (cp *ConnPool) Put(_ context.Context, body []byte, pri uint32, delay, ttr time.Duration) (uint64, error) {
- cp.RLock()
- defer cp.RUnlock()
-
- // TODO(rustatian): redial based on the token
- id, err := cp.t.Put(body, pri, delay, ttr)
- if err != nil {
- // errN contains both, err and internal checkAndRedial error
- errN := cp.checkAndRedial(err)
- if errN != nil {
- return 0, errors.Errorf("err: %s\nerr redial: %s", err, errN)
- } else {
- // retry put only when we redialed
- return cp.t.Put(body, pri, delay, ttr)
- }
- }
-
- return id, nil
-}
-
-// Reserve reserves and returns a job from one of the tubes in t. If no
-// job is available before time timeout has passed, Reserve returns a
-// ConnError recording ErrTimeout.
-//
-// Typically, a client will reserve a job, perform some work, then delete
-// the job with Conn.Delete.
-func (cp *ConnPool) Reserve(reserveTimeout time.Duration) (uint64, []byte, error) {
- cp.RLock()
- defer cp.RUnlock()
-
- id, body, err := cp.ts.Reserve(reserveTimeout)
- if err != nil {
- // errN contains both, err and internal checkAndRedial error
- errN := cp.checkAndRedial(err)
- if errN != nil {
- return 0, nil, errors.Errorf("err: %s\nerr redial: %s", err, errN)
- } else {
- // retry Reserve only when we redialed
- return cp.ts.Reserve(reserveTimeout)
- }
- }
-
- return id, body, nil
-}
-
-func (cp *ConnPool) Delete(_ context.Context, id uint64) error {
- cp.RLock()
- defer cp.RUnlock()
-
- err := cp.conn.Delete(id)
- if err != nil {
- // errN contains both, err and internal checkAndRedial error
- errN := cp.checkAndRedial(err)
- if errN != nil {
- return errors.Errorf("err: %s\nerr redial: %s", err, errN)
- } else {
- // retry Delete only when we redialed
- return cp.conn.Delete(id)
- }
- }
- return nil
-}
-
-func (cp *ConnPool) Stats(_ context.Context) (map[string]string, error) {
- cp.RLock()
- defer cp.RUnlock()
-
- stat, err := cp.conn.Stats()
- if err != nil {
- errR := cp.checkAndRedial(err)
- if errR != nil {
- return nil, errors.Errorf("err: %s\nerr redial: %s", err, errR)
- } else {
- return cp.conn.Stats()
- }
- }
-
- return stat, nil
-}
-
-func (cp *ConnPool) redial() error {
- const op = errors.Op("connection_pool_redial")
-
- cp.Lock()
- // backoff here
- expb := backoff.NewExponentialBackOff()
- // TODO(rustatian) set via config
- expb.MaxElapsedTime = time.Minute
-
- operation := func() error {
- connT, err := beanstalk.DialTimeout(cp.network, cp.address, cp.tout)
- if err != nil {
- return err
- }
- if connT == nil {
- return errors.E(op, errors.Str("connectionT is nil"))
- }
-
- connTS, err := beanstalk.DialTimeout(cp.network, cp.address, cp.tout)
- if err != nil {
- return err
- }
-
- if connTS == nil {
- return errors.E(op, errors.Str("connectionTS is nil"))
- }
-
- cp.t = beanstalk.NewTube(connT, cp.tName)
- cp.ts = beanstalk.NewTubeSet(connTS, cp.tName)
- cp.conn = connTS
- cp.connT = connT
-
- cp.log.Info("beanstalk redial was successful")
- return nil
- }
-
- retryErr := backoff.Retry(operation, expb)
- if retryErr != nil {
- cp.Unlock()
- return retryErr
- }
- cp.Unlock()
-
- return nil
-}
-
-var connErrors = map[string]struct{}{"EOF": {}}
-
-func (cp *ConnPool) checkAndRedial(err error) error {
- const op = errors.Op("connection_pool_check_redial")
- switch et := err.(type) { //nolint:gocritic
- // check if the error
- case beanstalk.ConnError:
- switch bErr := et.Err.(type) {
- case *net.OpError:
- cp.RUnlock()
- errR := cp.redial()
- cp.RLock()
- // if redial failed - return
- if errR != nil {
- return errors.E(op, errors.Errorf("%v:%v", bErr, errR))
- }
-
- // if redial was successful -> continue listening
- return nil
- default:
- if _, ok := connErrors[et.Err.Error()]; ok {
- // if error is related to the broken connection - redial
- cp.RUnlock()
- errR := cp.redial()
- cp.RLock()
- // if redial failed - return
- if errR != nil {
- return errors.E(op, errors.Errorf("%v:%v", err, errR))
- }
- // if redial was successful -> continue listening
- return nil
- }
- }
- }
-
- // return initial error
- return err
-}
diff --git a/plugins/beanstalk/consumer.go b/plugins/beanstalk/consumer.go
deleted file mode 100644
index 30807f03..00000000
--- a/plugins/beanstalk/consumer.go
+++ /dev/null
@@ -1,374 +0,0 @@
-package beanstalk
-
-import (
- "bytes"
- "context"
- "encoding/gob"
- "strconv"
- "strings"
- "sync/atomic"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- jobState "github.com/spiral/roadrunner/v2/pkg/state/job"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-type consumer struct {
- log logger.Logger
- eh events.Handler
- pq priorityqueue.Queue
-
- pipeline atomic.Value
- listeners uint32
-
- // beanstalk
- pool *ConnPool
- addr string
- network string
- reserveTimeout time.Duration
- reconnectCh chan struct{}
- tout time.Duration
- // tube name
- tName string
- tubePriority *uint32
- priority int64
-
- stopCh chan struct{}
- requeueCh chan *Item
-}
-
-func NewBeanstalkConsumer(configKey string, log logger.Logger, cfg config.Configurer, e events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("new_beanstalk_consumer")
-
- // PARSE CONFIGURATION -------
- var pipeCfg Config
- var globalCfg GlobalCfg
-
- if !cfg.Has(configKey) {
- return nil, errors.E(op, errors.Errorf("no configuration by provided key: %s", configKey))
- }
-
- // if no global section
- if !cfg.Has(pluginName) {
- return nil, errors.E(op, errors.Str("no global beanstalk configuration, global configuration should contain beanstalk addrs and timeout"))
- }
-
- err := cfg.UnmarshalKey(configKey, &pipeCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- pipeCfg.InitDefault()
-
- err = cfg.UnmarshalKey(pluginName, &globalCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- globalCfg.InitDefault()
-
- // PARSE CONFIGURATION -------
-
- dsn := strings.Split(globalCfg.Addr, "://")
- if len(dsn) != 2 {
- return nil, errors.E(op, errors.Errorf("invalid socket DSN (tcp://127.0.0.1:11300, unix://beanstalk.sock), provided: %s", globalCfg.Addr))
- }
-
- cPool, err := NewConnPool(dsn[0], dsn[1], pipeCfg.Tube, globalCfg.Timeout, log)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // initialize job consumer
- jc := &consumer{
- pq: pq,
- log: log,
- eh: e,
- pool: cPool,
- network: dsn[0],
- addr: dsn[1],
- tout: globalCfg.Timeout,
- tName: pipeCfg.Tube,
- reserveTimeout: pipeCfg.ReserveTimeout,
- tubePriority: pipeCfg.TubePriority,
- priority: pipeCfg.PipePriority,
-
- // buffered with two because jobs root plugin can call Stop at the same time as Pause
- stopCh: make(chan struct{}, 2),
- requeueCh: make(chan *Item, 1000),
- reconnectCh: make(chan struct{}, 2),
- }
-
- return jc, nil
-}
-
-func FromPipeline(pipe *pipeline.Pipeline, log logger.Logger, cfg config.Configurer, e events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("new_beanstalk_consumer")
-
- // PARSE CONFIGURATION -------
- var globalCfg GlobalCfg
-
- // if no global section
- if !cfg.Has(pluginName) {
- return nil, errors.E(op, errors.Str("no global beanstalk configuration, global configuration should contain beanstalk addrs and timeout"))
- }
-
- err := cfg.UnmarshalKey(pluginName, &globalCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- globalCfg.InitDefault()
-
- // PARSE CONFIGURATION -------
-
- dsn := strings.Split(globalCfg.Addr, "://")
- if len(dsn) != 2 {
- return nil, errors.E(op, errors.Errorf("invalid socket DSN (tcp://127.0.0.1:11300, unix://beanstalk.sock), provided: %s", globalCfg.Addr))
- }
-
- cPool, err := NewConnPool(dsn[0], dsn[1], pipe.String(tube, "default"), globalCfg.Timeout, log)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // initialize job consumer
- jc := &consumer{
- pq: pq,
- log: log,
- eh: e,
- pool: cPool,
- network: dsn[0],
- addr: dsn[1],
- tout: globalCfg.Timeout,
- tName: pipe.String(tube, "default"),
- reserveTimeout: time.Second * time.Duration(pipe.Int(reserveTimeout, 5)),
- tubePriority: utils.Uint32(uint32(pipe.Int(tubePriority, 1))),
- priority: pipe.Priority(),
-
- // buffered with two because jobs root plugin can call Stop at the same time as Pause
- stopCh: make(chan struct{}, 2),
- requeueCh: make(chan *Item, 1000),
- reconnectCh: make(chan struct{}, 2),
- }
-
- return jc, nil
-}
-func (j *consumer) Push(ctx context.Context, jb *job.Job) error {
- const op = errors.Op("beanstalk_push")
- // check if the pipeline registered
-
- // load atomic value
- pipe := j.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != jb.Options.Pipeline {
- return errors.E(op, errors.Errorf("no such pipeline: %s, actual: %s", jb.Options.Pipeline, pipe.Name()))
- }
-
- err := j.handleItem(ctx, fromJob(jb))
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (j *consumer) handleItem(ctx context.Context, item *Item) error {
- const op = errors.Op("beanstalk_handle_item")
-
- bb := new(bytes.Buffer)
- bb.Grow(64)
- err := gob.NewEncoder(bb).Encode(item)
- if err != nil {
- return errors.E(op, err)
- }
-
- body := make([]byte, bb.Len())
- copy(body, bb.Bytes())
- bb.Reset()
- bb = nil
-
- // https://github.com/beanstalkd/beanstalkd/blob/master/doc/protocol.txt#L458
- // <pri> is an integer < 2**32. Jobs with smaller priority values will be
- // scheduled before jobs with larger priorities. The most urgent priority is 0;
- // the least urgent priority is 4,294,967,295.
- //
- // <delay> is an integer number of seconds to wait before putting the job in
- // the ready queue. The job will be in the "delayed" state during this time.
- // Maximum delay is 2**32-1.
- //
- // <ttr> -- time to run -- is an integer number of seconds to allow a worker
- // to run this job. This time is counted from the moment a worker reserves
- // this job. If the worker does not delete, release, or bury the job within
- // <ttr> seconds, the job will time out and the server will release the job.
- // The minimum ttr is 1. If the client sends 0, the server will silently
- // increase the ttr to 1. Maximum ttr is 2**32-1.
- id, err := j.pool.Put(ctx, body, *j.tubePriority, item.Options.DelayDuration(), j.tout)
- if err != nil {
- errD := j.pool.Delete(ctx, id)
- if errD != nil {
- return errors.E(op, errors.Errorf("%s:%s", err.Error(), errD.Error()))
- }
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (j *consumer) Register(_ context.Context, p *pipeline.Pipeline) error {
- // register the pipeline
- j.pipeline.Store(p)
- return nil
-}
-
-// State https://github.com/beanstalkd/beanstalkd/blob/master/doc/protocol.txt#L514
-func (j *consumer) State(ctx context.Context) (*jobState.State, error) {
- const op = errors.Op("beanstalk_state")
- stat, err := j.pool.Stats(ctx)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- pipe := j.pipeline.Load().(*pipeline.Pipeline)
-
- out := &jobState.State{
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Queue: j.tName,
- Ready: ready(atomic.LoadUint32(&j.listeners)),
- }
-
- // set stat, skip errors (replace with 0)
- // https://github.com/beanstalkd/beanstalkd/blob/master/doc/protocol.txt#L523
- if v, err := strconv.Atoi(stat["current-jobs-ready"]); err == nil {
- out.Active = int64(v)
- }
-
- // https://github.com/beanstalkd/beanstalkd/blob/master/doc/protocol.txt#L525
- if v, err := strconv.Atoi(stat["current-jobs-reserved"]); err == nil {
- // this is not an error, reserved in beanstalk behaves like an active jobs
- out.Reserved = int64(v)
- }
-
- // https://github.com/beanstalkd/beanstalkd/blob/master/doc/protocol.txt#L528
- if v, err := strconv.Atoi(stat["current-jobs-delayed"]); err == nil {
- out.Delayed = int64(v)
- }
-
- return out, nil
-}
-
-func (j *consumer) Run(_ context.Context, p *pipeline.Pipeline) error {
- const op = errors.Op("beanstalk_run")
- start := time.Now()
-
- // load atomic value
- // check if the pipeline registered
- pipe := j.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p.Name() {
- return errors.E(op, errors.Errorf("no such pipeline: %s, actual: %s", p.Name(), pipe.Name()))
- }
-
- atomic.AddUint32(&j.listeners, 1)
-
- go j.listen()
-
- j.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- return nil
-}
-
-func (j *consumer) Stop(context.Context) error {
- start := time.Now()
- pipe := j.pipeline.Load().(*pipeline.Pipeline)
-
- if atomic.LoadUint32(&j.listeners) == 1 {
- j.stopCh <- struct{}{}
- }
-
- j.eh.Push(events.JobEvent{
- Event: events.EventPipeStopped,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- return nil
-}
-
-func (j *consumer) Pause(_ context.Context, p string) {
- start := time.Now()
- // load atomic value
- pipe := j.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- j.log.Error("no such pipeline", "requested", p, "actual", pipe.Name())
- return
- }
-
- l := atomic.LoadUint32(&j.listeners)
- // no active listeners
- if l == 0 {
- j.log.Warn("no active listeners, nothing to pause")
- return
- }
-
- atomic.AddUint32(&j.listeners, ^uint32(0))
-
- j.stopCh <- struct{}{}
-
- j.eh.Push(events.JobEvent{
- Event: events.EventPipePaused,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (j *consumer) Resume(_ context.Context, p string) {
- start := time.Now()
- // load atomic value
- pipe := j.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- j.log.Error("no such pipeline", "requested", p, "actual", pipe.Name())
- return
- }
-
- l := atomic.LoadUint32(&j.listeners)
- // no active listeners
- if l == 1 {
- j.log.Warn("sqs listener already in the active state")
- return
- }
-
- // start listener
- go j.listen()
-
- // increase num of listeners
- atomic.AddUint32(&j.listeners, 1)
-
- j.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func ready(r uint32) bool {
- return r > 0
-}
diff --git a/plugins/beanstalk/encode_test.go b/plugins/beanstalk/encode_test.go
deleted file mode 100644
index e43207eb..00000000
--- a/plugins/beanstalk/encode_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package beanstalk
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/gob"
- "testing"
-
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-func BenchmarkEncodeGob(b *testing.B) {
- tb := make([]byte, 1024*10)
- _, err := rand.Read(tb)
- if err != nil {
- b.Fatal(err)
- }
-
- item := &Item{
- Job: "/super/test/php/class/loooooong",
- Ident: "12341234-asdfasdfa-1234234-asdfasdfas",
- Payload: utils.AsString(tb),
- Headers: map[string][]string{"Test": {"test1", "test2"}},
- Options: &Options{
- Priority: 10,
- Pipeline: "test-local-pipe",
- Delay: 10,
- },
- }
-
- b.ResetTimer()
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- bb := new(bytes.Buffer)
- err := gob.NewEncoder(bb).Encode(item)
- if err != nil {
- b.Fatal(err)
- }
- _ = bb.Bytes()
- bb.Reset()
- }
-}
-
-func BenchmarkEncodeJsonIter(b *testing.B) {
- tb := make([]byte, 1024*10)
- _, err := rand.Read(tb)
- if err != nil {
- b.Fatal(err)
- }
-
- item := &Item{
- Job: "/super/test/php/class/loooooong",
- Ident: "12341234-asdfasdfa-1234234-asdfasdfas",
- Payload: utils.AsString(tb),
- Headers: map[string][]string{"Test": {"test1", "test2"}},
- Options: &Options{
- Priority: 10,
- Pipeline: "test-local-pipe",
- Delay: 10,
- },
- }
-
- b.ResetTimer()
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- bb, err := json.Marshal(item)
- if err != nil {
- b.Fatal(err)
- }
- _ = bb
- }
-}
diff --git a/plugins/beanstalk/item.go b/plugins/beanstalk/item.go
deleted file mode 100644
index 03060994..00000000
--- a/plugins/beanstalk/item.go
+++ /dev/null
@@ -1,138 +0,0 @@
-package beanstalk
-
-import (
- "bytes"
- "context"
- "encoding/gob"
- "time"
-
- "github.com/beanstalkd/go-beanstalk"
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-type Item struct {
- // Job contains pluginName of job broker (usually PHP class).
- Job string `json:"job"`
-
- // Ident is unique identifier of the job, should be provided from outside
- Ident string `json:"id"`
-
- // Payload is string data (usually JSON) passed to Job broker.
- Payload string `json:"payload"`
-
- // Headers with key-values pairs
- Headers map[string][]string `json:"headers"`
-
- // Options contains set of PipelineOptions specific to job execution. Can be empty.
- Options *Options `json:"options,omitempty"`
-}
-
-// Options carry information about how to handle given job.
-type Options struct {
- // Priority is job priority, default - 10
- // pointer to distinguish 0 as a priority and nil as priority not set
- Priority int64 `json:"priority"`
-
- // Pipeline manually specified pipeline.
- Pipeline string `json:"pipeline,omitempty"`
-
- // Delay defines time duration to delay execution for. Defaults to none.
- Delay int64 `json:"delay,omitempty"`
-
- // Private ================
- id uint64
- conn *beanstalk.Conn
- requeueFn func(context.Context, *Item) error
-}
-
-// DelayDuration returns delay duration in a form of time.Duration.
-func (o *Options) DelayDuration() time.Duration {
- return time.Second * time.Duration(o.Delay)
-}
-
-func (i *Item) ID() string {
- return i.Ident
-}
-
-func (i *Item) Priority() int64 {
- return i.Options.Priority
-}
-
-// Body packs job payload into binary payload.
-func (i *Item) Body() []byte {
- return utils.AsBytes(i.Payload)
-}
-
-// Context packs job context (job, id) into binary payload.
-// Not used in the sqs, MessageAttributes used instead
-func (i *Item) Context() ([]byte, error) {
- ctx, err := json.Marshal(
- struct {
- ID string `json:"id"`
- Job string `json:"job"`
- Headers map[string][]string `json:"headers"`
- Pipeline string `json:"pipeline"`
- }{ID: i.Ident, Job: i.Job, Headers: i.Headers, Pipeline: i.Options.Pipeline},
- )
-
- if err != nil {
- return nil, err
- }
-
- return ctx, nil
-}
-
-func (i *Item) Ack() error {
- return i.Options.conn.Delete(i.Options.id)
-}
-
-func (i *Item) Nack() error {
- return i.Options.conn.Delete(i.Options.id)
-}
-
-func (i *Item) Requeue(headers map[string][]string, delay int64) error {
- // overwrite the delay
- i.Options.Delay = delay
- i.Headers = headers
-
- err := i.Options.requeueFn(context.Background(), i)
- if err != nil {
- return err
- }
-
- // delete old job
- err = i.Options.conn.Delete(i.Options.id)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func fromJob(job *job.Job) *Item {
- return &Item{
- Job: job.Job,
- Ident: job.Ident,
- Payload: job.Payload,
- Headers: job.Headers,
- Options: &Options{
- Priority: job.Options.Priority,
- Pipeline: job.Options.Pipeline,
- Delay: job.Options.Delay,
- },
- }
-}
-
-func (j *consumer) unpack(id uint64, data []byte, out *Item) error {
- err := gob.NewDecoder(bytes.NewBuffer(data)).Decode(out)
- if err != nil {
- return err
- }
- out.Options.conn = j.pool.conn
- out.Options.id = id
- out.Options.requeueFn = j.handleItem
-
- return nil
-}
diff --git a/plugins/beanstalk/listen.go b/plugins/beanstalk/listen.go
deleted file mode 100644
index 6bb159ea..00000000
--- a/plugins/beanstalk/listen.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package beanstalk
-
-import (
- "github.com/beanstalkd/go-beanstalk"
-)
-
-func (j *consumer) listen() {
- for {
- select {
- case <-j.stopCh:
- j.log.Warn("beanstalk listener stopped")
- return
- default:
- id, body, err := j.pool.Reserve(j.reserveTimeout)
- if err != nil {
- if errB, ok := err.(beanstalk.ConnError); ok {
- switch errB.Err { //nolint:gocritic
- case beanstalk.ErrTimeout:
- j.log.Info("beanstalk reserve timeout", "warn", errB.Op)
- continue
- }
- }
- // in case of other error - continue
- j.log.Error("beanstalk reserve", "error", err)
- continue
- }
-
- item := &Item{}
- err = j.unpack(id, body, item)
- if err != nil {
- j.log.Error("beanstalk unpack item", "error", err)
- continue
- }
-
- // insert job into the priority queue
- j.pq.Insert(item)
- }
- }
-}
diff --git a/plugins/beanstalk/plugin.go b/plugins/beanstalk/plugin.go
deleted file mode 100644
index 529d1474..00000000
--- a/plugins/beanstalk/plugin.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package beanstalk
-
-import (
- "github.com/spiral/roadrunner/v2/common/jobs"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const (
- pluginName string = "beanstalk"
-)
-
-type Plugin struct {
- log logger.Logger
- cfg config.Configurer
-}
-
-func (p *Plugin) Init(log logger.Logger, cfg config.Configurer) error {
- p.log = log
- p.cfg = cfg
- return nil
-}
-
-func (p *Plugin) Serve() chan error {
- return make(chan error)
-}
-
-func (p *Plugin) Stop() error {
- return nil
-}
-
-func (p *Plugin) Name() string {
- return pluginName
-}
-
-func (p *Plugin) Available() {}
-
-func (p *Plugin) JobsConstruct(configKey string, eh events.Handler, pq priorityqueue.Queue) (jobs.Consumer, error) {
- return NewBeanstalkConsumer(configKey, p.log, p.cfg, eh, pq)
-}
-
-func (p *Plugin) FromPipeline(pipe *pipeline.Pipeline, eh events.Handler, pq priorityqueue.Queue) (jobs.Consumer, error) {
- return FromPipeline(pipe, p.log, p.cfg, eh, pq)
-}
diff --git a/plugins/boltdb/boltjobs/config.go b/plugins/boltdb/boltjobs/config.go
deleted file mode 100644
index 8cc098c1..00000000
--- a/plugins/boltdb/boltjobs/config.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package boltjobs
-
-const (
- file string = "file"
- priority string = "priority"
- prefetch string = "prefetch"
-)
-
-type GlobalCfg struct {
- // db file permissions
- Permissions int `mapstructure:"permissions"`
- // consume timeout
-}
-
-func (c *GlobalCfg) InitDefaults() {
- if c.Permissions == 0 {
- c.Permissions = 0777
- }
-}
-
-type Config struct {
- File string `mapstructure:"file"`
- Priority int `mapstructure:"priority"`
- Prefetch int `mapstructure:"prefetch"`
-}
-
-func (c *Config) InitDefaults() {
- if c.File == "" {
- c.File = "rr.db"
- }
-
- if c.Priority == 0 {
- c.Priority = 10
- }
-
- if c.Prefetch == 0 {
- c.Prefetch = 1000
- }
-}
diff --git a/plugins/boltdb/boltjobs/consumer.go b/plugins/boltdb/boltjobs/consumer.go
deleted file mode 100644
index 62045d3b..00000000
--- a/plugins/boltdb/boltjobs/consumer.go
+++ /dev/null
@@ -1,430 +0,0 @@
-package boltjobs
-
-import (
- "bytes"
- "context"
- "encoding/gob"
- "os"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- jobState "github.com/spiral/roadrunner/v2/pkg/state/job"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/utils"
- bolt "go.etcd.io/bbolt"
-)
-
-const (
- PluginName string = "boltdb"
- rrDB string = "rr.db"
-
- PushBucket string = "push"
- InQueueBucket string = "processing"
- DelayBucket string = "delayed"
-)
-
-type consumer struct {
- file string
- permissions int
- priority int
- prefetch int
-
- db *bolt.DB
-
- bPool sync.Pool
- log logger.Logger
- eh events.Handler
- pq priorityqueue.Queue
- pipeline atomic.Value
- cond *sync.Cond
-
- listeners uint32
- active *uint64
- delayed *uint64
-
- stopCh chan struct{}
-}
-
-func NewBoltDBJobs(configKey string, log logger.Logger, cfg config.Configurer, e events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("init_boltdb_jobs")
-
- if !cfg.Has(configKey) {
- return nil, errors.E(op, errors.Errorf("no configuration by provided key: %s", configKey))
- }
-
- // if no global section
- if !cfg.Has(PluginName) {
- return nil, errors.E(op, errors.Str("no global boltdb configuration"))
- }
-
- conf := &GlobalCfg{}
- err := cfg.UnmarshalKey(PluginName, conf)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- localCfg := &Config{}
- err = cfg.UnmarshalKey(configKey, localCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- localCfg.InitDefaults()
- conf.InitDefaults()
-
- db, err := bolt.Open(localCfg.File, os.FileMode(conf.Permissions), &bolt.Options{
- Timeout: time.Second * 20,
- NoGrowSync: false,
- NoFreelistSync: false,
- ReadOnly: false,
- NoSync: false,
- })
-
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // create bucket if it does not exist
- // tx.Commit invokes via the db.Update
- err = db.Update(func(tx *bolt.Tx) error {
- const upOp = errors.Op("boltdb_plugin_update")
- _, err = tx.CreateBucketIfNotExists(utils.AsBytes(DelayBucket))
- if err != nil {
- return errors.E(op, upOp)
- }
-
- _, err = tx.CreateBucketIfNotExists(utils.AsBytes(PushBucket))
- if err != nil {
- return errors.E(op, upOp)
- }
-
- _, err = tx.CreateBucketIfNotExists(utils.AsBytes(InQueueBucket))
- if err != nil {
- return errors.E(op, upOp)
- }
-
- inQb := tx.Bucket(utils.AsBytes(InQueueBucket))
- cursor := inQb.Cursor()
-
- pushB := tx.Bucket(utils.AsBytes(PushBucket))
-
- // get all items, which are in the InQueueBucket and put them into the PushBucket
- for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
- err = pushB.Put(k, v)
- if err != nil {
- return errors.E(op, err)
- }
- }
- return nil
- })
-
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return &consumer{
- permissions: conf.Permissions,
- file: localCfg.File,
- priority: localCfg.Priority,
- prefetch: localCfg.Prefetch,
-
- bPool: sync.Pool{New: func() interface{} {
- return new(bytes.Buffer)
- }},
- cond: sync.NewCond(&sync.Mutex{}),
-
- delayed: utils.Uint64(0),
- active: utils.Uint64(0),
-
- db: db,
- log: log,
- eh: e,
- pq: pq,
- stopCh: make(chan struct{}, 2),
- }, nil
-}
-
-func FromPipeline(pipeline *pipeline.Pipeline, log logger.Logger, cfg config.Configurer, e events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("init_boltdb_jobs")
-
- // if no global section
- if !cfg.Has(PluginName) {
- return nil, errors.E(op, errors.Str("no global boltdb configuration"))
- }
-
- conf := &GlobalCfg{}
- err := cfg.UnmarshalKey(PluginName, conf)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // add default values
- conf.InitDefaults()
-
- db, err := bolt.Open(pipeline.String(file, rrDB), os.FileMode(conf.Permissions), &bolt.Options{
- Timeout: time.Second * 20,
- NoGrowSync: false,
- NoFreelistSync: false,
- ReadOnly: false,
- NoSync: false,
- })
-
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // create bucket if it does not exist
- // tx.Commit invokes via the db.Update
- err = db.Update(func(tx *bolt.Tx) error {
- const upOp = errors.Op("boltdb_plugin_update")
- _, err = tx.CreateBucketIfNotExists(utils.AsBytes(DelayBucket))
- if err != nil {
- return errors.E(op, upOp)
- }
-
- _, err = tx.CreateBucketIfNotExists(utils.AsBytes(PushBucket))
- if err != nil {
- return errors.E(op, upOp)
- }
-
- _, err = tx.CreateBucketIfNotExists(utils.AsBytes(InQueueBucket))
- if err != nil {
- return errors.E(op, upOp)
- }
-
- inQb := tx.Bucket(utils.AsBytes(InQueueBucket))
- cursor := inQb.Cursor()
-
- pushB := tx.Bucket(utils.AsBytes(PushBucket))
-
- // get all items, which are in the InQueueBucket and put them into the PushBucket
- for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
- err = pushB.Put(k, v)
- if err != nil {
- return errors.E(op, err)
- }
- }
-
- return nil
- })
-
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return &consumer{
- file: pipeline.String(file, rrDB),
- priority: pipeline.Int(priority, 10),
- prefetch: pipeline.Int(prefetch, 1000),
- permissions: conf.Permissions,
-
- bPool: sync.Pool{New: func() interface{} {
- return new(bytes.Buffer)
- }},
- cond: sync.NewCond(&sync.Mutex{}),
-
- delayed: utils.Uint64(0),
- active: utils.Uint64(0),
-
- db: db,
- log: log,
- eh: e,
- pq: pq,
- stopCh: make(chan struct{}, 2),
- }, nil
-}
-
-func (c *consumer) Push(_ context.Context, job *job.Job) error {
- const op = errors.Op("boltdb_jobs_push")
- err := c.db.Update(func(tx *bolt.Tx) error {
- item := fromJob(job)
- // pool with buffers
- buf := c.get()
- // encode the job
- enc := gob.NewEncoder(buf)
- err := enc.Encode(item)
- if err != nil {
- c.put(buf)
- return errors.E(op, err)
- }
-
- value := make([]byte, buf.Len())
- copy(value, buf.Bytes())
- c.put(buf)
-
- // handle delay
- if item.Options.Delay > 0 {
- b := tx.Bucket(utils.AsBytes(DelayBucket))
- tKey := time.Now().UTC().Add(time.Second * time.Duration(item.Options.Delay)).Format(time.RFC3339)
-
- err = b.Put(utils.AsBytes(tKey), value)
- if err != nil {
- return errors.E(op, err)
- }
-
- atomic.AddUint64(c.delayed, 1)
-
- return nil
- }
-
- b := tx.Bucket(utils.AsBytes(PushBucket))
- err = b.Put(utils.AsBytes(item.ID()), value)
- if err != nil {
- return errors.E(op, err)
- }
-
- // increment active counter
- atomic.AddUint64(c.active, 1)
-
- return nil
- })
-
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (c *consumer) Register(_ context.Context, pipeline *pipeline.Pipeline) error {
- c.pipeline.Store(pipeline)
- return nil
-}
-
-func (c *consumer) Run(_ context.Context, p *pipeline.Pipeline) error {
- const op = errors.Op("boltdb_run")
- start := time.Now()
-
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p.Name() {
- return errors.E(op, errors.Errorf("no such pipeline registered: %s", pipe.Name()))
- }
-
- // run listener
- go c.listener()
- go c.delayedJobsListener()
-
- // increase number of listeners
- atomic.AddUint32(&c.listeners, 1)
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- return nil
-}
-
-func (c *consumer) Stop(_ context.Context) error {
- start := time.Now()
- if atomic.LoadUint32(&c.listeners) > 0 {
- c.stopCh <- struct{}{}
- c.stopCh <- struct{}{}
- }
-
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeStopped,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
- return nil
-}
-
-func (c *consumer) Pause(_ context.Context, p string) {
- start := time.Now()
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- c.log.Error("no such pipeline", "requested pause on: ", p)
- }
-
- l := atomic.LoadUint32(&c.listeners)
- // no active listeners
- if l == 0 {
- c.log.Warn("no active listeners, nothing to pause")
- return
- }
-
- c.stopCh <- struct{}{}
- c.stopCh <- struct{}{}
-
- atomic.AddUint32(&c.listeners, ^uint32(0))
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipePaused,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (c *consumer) Resume(_ context.Context, p string) {
- start := time.Now()
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- c.log.Error("no such pipeline", "requested resume on: ", p)
- }
-
- l := atomic.LoadUint32(&c.listeners)
- // no active listeners
- if l == 1 {
- c.log.Warn("amqp listener already in the active state")
- return
- }
-
- // run listener
- go c.listener()
- go c.delayedJobsListener()
-
- // increase number of listeners
- atomic.AddUint32(&c.listeners, 1)
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (c *consumer) State(_ context.Context) (*jobState.State, error) {
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
-
- return &jobState.State{
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Queue: PushBucket,
- Active: int64(atomic.LoadUint64(c.active)),
- Delayed: int64(atomic.LoadUint64(c.delayed)),
- Ready: toBool(atomic.LoadUint32(&c.listeners)),
- }, nil
-}
-
-// Private
-
-func (c *consumer) get() *bytes.Buffer {
- return c.bPool.Get().(*bytes.Buffer)
-}
-
-func (c *consumer) put(b *bytes.Buffer) {
- b.Reset()
- c.bPool.Put(b)
-}
-
-func toBool(r uint32) bool {
- return r > 0
-}
diff --git a/plugins/boltdb/boltjobs/item.go b/plugins/boltdb/boltjobs/item.go
deleted file mode 100644
index 837f8c63..00000000
--- a/plugins/boltdb/boltjobs/item.go
+++ /dev/null
@@ -1,229 +0,0 @@
-package boltjobs
-
-import (
- "bytes"
- "encoding/gob"
- "sync/atomic"
- "time"
-
- json "github.com/json-iterator/go"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/utils"
- "go.etcd.io/bbolt"
-)
-
-type Item struct {
- // Job contains pluginName of job broker (usually PHP class).
- Job string `json:"job"`
-
- // Ident is unique identifier of the job, should be provided from outside
- Ident string `json:"id"`
-
- // Payload is string data (usually JSON) passed to Job broker.
- Payload string `json:"payload"`
-
- // Headers with key-values pairs
- Headers map[string][]string `json:"headers"`
-
- // Options contains set of PipelineOptions specific to job execution. Can be empty.
- Options *Options `json:"options,omitempty"`
-}
-
-// Options carry information about how to handle given job.
-type Options struct {
- // Priority is job priority, default - 10
- // pointer to distinguish 0 as a priority and nil as priority not set
- Priority int64 `json:"priority"`
-
- // Pipeline manually specified pipeline.
- Pipeline string `json:"pipeline,omitempty"`
-
- // Delay defines time duration to delay execution for. Defaults to none.
- Delay int64 `json:"delay,omitempty"`
-
- // private
- db *bbolt.DB
- active *uint64
- delayed *uint64
-}
-
-func (i *Item) ID() string {
- return i.Ident
-}
-
-func (i *Item) Priority() int64 {
- return i.Options.Priority
-}
-
-func (i *Item) Body() []byte {
- return utils.AsBytes(i.Payload)
-}
-
-func (i *Item) Context() ([]byte, error) {
- ctx, err := json.Marshal(
- struct {
- ID string `json:"id"`
- Job string `json:"job"`
- Headers map[string][]string `json:"headers"`
- Pipeline string `json:"pipeline"`
- }{ID: i.Ident, Job: i.Job, Headers: i.Headers, Pipeline: i.Options.Pipeline},
- )
-
- if err != nil {
- return nil, err
- }
-
- return ctx, nil
-}
-
-func (i *Item) Ack() error {
- const op = errors.Op("boltdb_item_ack")
- tx, err := i.Options.db.Begin(true)
- if err != nil {
- _ = tx.Rollback()
- return errors.E(op, err)
- }
-
- inQb := tx.Bucket(utils.AsBytes(InQueueBucket))
- err = inQb.Delete(utils.AsBytes(i.ID()))
- if err != nil {
- _ = tx.Rollback()
- return errors.E(op, err)
- }
-
- if i.Options.Delay > 0 {
- atomic.AddUint64(i.Options.delayed, ^uint64(0))
- } else {
- atomic.AddUint64(i.Options.active, ^uint64(0))
- }
-
- return tx.Commit()
-}
-
-func (i *Item) Nack() error {
- const op = errors.Op("boltdb_item_ack")
- /*
- steps:
- 1. begin tx
- 2. get item by ID from the InQueueBucket (previously put in the listener)
- 3. put it back to the PushBucket
- 4. Delete it from the InQueueBucket
- */
- tx, err := i.Options.db.Begin(true)
- if err != nil {
- _ = tx.Rollback()
- return errors.E(op, err)
- }
-
- inQb := tx.Bucket(utils.AsBytes(InQueueBucket))
- v := inQb.Get(utils.AsBytes(i.ID()))
-
- pushB := tx.Bucket(utils.AsBytes(PushBucket))
-
- err = pushB.Put(utils.AsBytes(i.ID()), v)
- if err != nil {
- _ = tx.Rollback()
- return errors.E(op, err)
- }
-
- err = inQb.Delete(utils.AsBytes(i.ID()))
- if err != nil {
- _ = tx.Rollback()
- return errors.E(op, err)
- }
-
- return tx.Commit()
-}
-
-/*
-Requeue algorithm:
-1. Rewrite item headers and delay.
-2. Begin writable transaction on attached to the item db.
-3. Delete item from the InQueueBucket
-4. Handle items with the delay:
- 4.1. Get DelayBucket
- 4.2. Make a key by adding the delay to the time.Now() in RFC3339 format
- 4.3. Put this key with value to the DelayBucket
-5. W/o delay, put the key with value to the PushBucket (requeue)
-*/
-func (i *Item) Requeue(headers map[string][]string, delay int64) error {
- const op = errors.Op("boltdb_item_requeue")
- i.Headers = headers
- i.Options.Delay = delay
-
- tx, err := i.Options.db.Begin(true)
- if err != nil {
- return errors.E(op, err)
- }
-
- inQb := tx.Bucket(utils.AsBytes(InQueueBucket))
- err = inQb.Delete(utils.AsBytes(i.ID()))
- if err != nil {
- return errors.E(op, i.rollback(err, tx))
- }
-
- // encode the item
- buf := new(bytes.Buffer)
- enc := gob.NewEncoder(buf)
- err = enc.Encode(i)
- val := make([]byte, buf.Len())
- copy(val, buf.Bytes())
- buf.Reset()
-
- if delay > 0 {
- delayB := tx.Bucket(utils.AsBytes(DelayBucket))
- tKey := time.Now().UTC().Add(time.Second * time.Duration(delay)).Format(time.RFC3339)
-
- if err != nil {
- return errors.E(op, i.rollback(err, tx))
- }
-
- err = delayB.Put(utils.AsBytes(tKey), val)
- if err != nil {
- return errors.E(op, i.rollback(err, tx))
- }
-
- return tx.Commit()
- }
-
- pushB := tx.Bucket(utils.AsBytes(PushBucket))
- if err != nil {
- return errors.E(op, i.rollback(err, tx))
- }
-
- err = pushB.Put(utils.AsBytes(i.ID()), val)
- if err != nil {
- return errors.E(op, i.rollback(err, tx))
- }
-
- return tx.Commit()
-}
-
-func (i *Item) attachDB(db *bbolt.DB, active, delayed *uint64) {
- i.Options.db = db
- i.Options.active = active
- i.Options.delayed = delayed
-}
-
-func (i *Item) rollback(err error, tx *bbolt.Tx) error {
- errR := tx.Rollback()
- if errR != nil {
- return errors.Errorf("transaction commit error: %v, rollback failed: %v", err, errR)
- }
- return errors.Errorf("transaction commit error: %v", err)
-}
-
-func fromJob(job *job.Job) *Item {
- return &Item{
- Job: job.Job,
- Ident: job.Ident,
- Payload: job.Payload,
- Headers: job.Headers,
- Options: &Options{
- Priority: job.Options.Priority,
- Pipeline: job.Options.Pipeline,
- Delay: job.Options.Delay,
- },
- }
-}
diff --git a/plugins/boltdb/boltjobs/listener.go b/plugins/boltdb/boltjobs/listener.go
deleted file mode 100644
index 081d3f57..00000000
--- a/plugins/boltdb/boltjobs/listener.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package boltjobs
-
-import (
- "bytes"
- "encoding/gob"
- "sync/atomic"
- "time"
-
- "github.com/spiral/roadrunner/v2/utils"
- bolt "go.etcd.io/bbolt"
-)
-
-func (c *consumer) listener() {
- tt := time.NewTicker(time.Millisecond)
- defer tt.Stop()
- for {
- select {
- case <-c.stopCh:
- c.log.Info("boltdb listener stopped")
- return
- case <-tt.C:
- if atomic.LoadUint64(c.active) > uint64(c.prefetch) {
- time.Sleep(time.Second)
- continue
- }
- tx, err := c.db.Begin(true)
- if err != nil {
- c.log.Error("failed to begin writable transaction, job will be read on the next attempt", "error", err)
- continue
- }
-
- b := tx.Bucket(utils.AsBytes(PushBucket))
- inQb := tx.Bucket(utils.AsBytes(InQueueBucket))
-
- // get first item
- k, v := b.Cursor().First()
- if k == nil && v == nil {
- _ = tx.Commit()
- continue
- }
-
- buf := bytes.NewReader(v)
- dec := gob.NewDecoder(buf)
-
- item := &Item{}
- err = dec.Decode(item)
- if err != nil {
- c.rollback(err, tx)
- continue
- }
-
- err = inQb.Put(utils.AsBytes(item.ID()), v)
- if err != nil {
- c.rollback(err, tx)
- continue
- }
-
- // delete key from the PushBucket
- err = b.Delete(k)
- if err != nil {
- c.rollback(err, tx)
- continue
- }
-
- err = tx.Commit()
- if err != nil {
- c.rollback(err, tx)
- continue
- }
-
- // attach pointer to the DB
- item.attachDB(c.db, c.active, c.delayed)
- // as the last step, after commit, put the item into the PQ
- c.pq.Insert(item)
- }
- }
-}
-
-func (c *consumer) delayedJobsListener() {
- tt := time.NewTicker(time.Second)
- defer tt.Stop()
-
- // just some 90's
- loc, err := time.LoadLocation("UTC")
- if err != nil {
- c.log.Error("failed to load location, delayed jobs won't work", "error", err)
- return
- }
-
- var startDate = utils.AsBytes(time.Date(1990, 1, 1, 0, 0, 0, 0, loc).Format(time.RFC3339))
-
- for {
- select {
- case <-c.stopCh:
- c.log.Info("boltdb listener stopped")
- return
- case <-tt.C:
- tx, err := c.db.Begin(true)
- if err != nil {
- c.log.Error("failed to begin writable transaction, job will be read on the next attempt", "error", err)
- continue
- }
-
- delayB := tx.Bucket(utils.AsBytes(DelayBucket))
- inQb := tx.Bucket(utils.AsBytes(InQueueBucket))
-
- cursor := delayB.Cursor()
- endDate := utils.AsBytes(time.Now().UTC().Format(time.RFC3339))
-
- for k, v := cursor.Seek(startDate); k != nil && bytes.Compare(k, endDate) <= 0; k, v = cursor.Next() {
- buf := bytes.NewReader(v)
- dec := gob.NewDecoder(buf)
-
- item := &Item{}
- err = dec.Decode(item)
- if err != nil {
- c.rollback(err, tx)
- continue
- }
-
- err = inQb.Put(utils.AsBytes(item.ID()), v)
- if err != nil {
- c.rollback(err, tx)
- continue
- }
-
- // delete key from the PushBucket
- err = delayB.Delete(k)
- if err != nil {
- c.rollback(err, tx)
- continue
- }
-
- // attach pointer to the DB
- item.attachDB(c.db, c.active, c.delayed)
- // as the last step, after commit, put the item into the PQ
- c.pq.Insert(item)
- }
-
- err = tx.Commit()
- if err != nil {
- c.rollback(err, tx)
- continue
- }
- }
- }
-}
-
-func (c *consumer) rollback(err error, tx *bolt.Tx) {
- errR := tx.Rollback()
- if errR != nil {
- c.log.Error("transaction commit error, rollback failed", "error", err, "rollback error", errR)
- }
-
- c.log.Error("transaction commit error, rollback succeed", "error", err)
-}
diff --git a/plugins/boltdb/boltkv/config.go b/plugins/boltdb/boltkv/config.go
deleted file mode 100644
index 56d00674..00000000
--- a/plugins/boltdb/boltkv/config.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package boltkv
-
-type Config struct {
- // File is boltDB file. No need to create it by your own,
- // boltdb driver is able to create the file, or read existing
- File string
- // Bucket to store data in boltDB
- bucket string
- // db file permissions
- Permissions int
- // timeout
- Interval int `mapstructure:"interval"`
-}
-
-// InitDefaults initializes default values for the boltdb
-func (s *Config) InitDefaults() {
- s.bucket = "default"
-
- if s.File == "" {
- s.File = "rr.db" // default file name
- }
-
- if s.Permissions == 0 {
- s.Permissions = 0777 // free for all
- }
-
- if s.Interval == 0 {
- s.Interval = 60 // default is 60 seconds timeout
- }
-}
diff --git a/plugins/boltdb/boltkv/driver.go b/plugins/boltdb/boltkv/driver.go
deleted file mode 100644
index 656d572e..00000000
--- a/plugins/boltdb/boltkv/driver.go
+++ /dev/null
@@ -1,472 +0,0 @@
-package boltkv
-
-import (
- "bytes"
- "encoding/gob"
- "os"
- "strings"
- "sync"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- kvv1 "github.com/spiral/roadrunner/v2/proto/kv/v1beta"
- "github.com/spiral/roadrunner/v2/utils"
- bolt "go.etcd.io/bbolt"
-)
-
-const (
- RootPluginName string = "kv"
-)
-
-type Driver struct {
- clearMu sync.RWMutex
- // db instance
- DB *bolt.DB
- // name should be UTF-8
- bucket []byte
- log logger.Logger
- cfg *Config
-
- // gc contains keys with timeouts
- gc sync.Map
- // default timeout for cache cleanup is 1 minute
- timeout time.Duration
-
- // stop is used to stop keys GC and close boltdb connection
- stop chan struct{}
-}
-
-func NewBoltDBDriver(log logger.Logger, key string, cfgPlugin config.Configurer) (*Driver, error) {
- const op = errors.Op("new_boltdb_driver")
-
- if !cfgPlugin.Has(RootPluginName) {
- return nil, errors.E(op, errors.Str("no kv section in the configuration"))
- }
-
- d := &Driver{
- log: log,
- stop: make(chan struct{}),
- }
-
- err := cfgPlugin.UnmarshalKey(key, &d.cfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // add default values
- d.cfg.InitDefaults()
-
- d.bucket = []byte(d.cfg.bucket)
- d.timeout = time.Duration(d.cfg.Interval) * time.Second
- d.gc = sync.Map{}
-
- db, err := bolt.Open(d.cfg.File, os.FileMode(d.cfg.Permissions), &bolt.Options{
- Timeout: time.Second * 20,
- NoGrowSync: false,
- NoFreelistSync: false,
- ReadOnly: false,
- NoSync: false,
- })
-
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- d.DB = db
-
- // create bucket if it does not exist
- // tx.Commit invokes via the db.Update
- err = db.Update(func(tx *bolt.Tx) error {
- const upOp = errors.Op("boltdb_plugin_update")
- _, err = tx.CreateBucketIfNotExists([]byte(d.cfg.bucket))
- if err != nil {
- return errors.E(op, upOp)
- }
- return nil
- })
-
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- go d.startGCLoop()
-
- return d, nil
-}
-
-func (d *Driver) Has(keys ...string) (map[string]bool, error) {
- const op = errors.Op("boltdb_driver_has")
- d.log.Debug("boltdb HAS method called", "args", keys)
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- m := make(map[string]bool, len(keys))
-
- // this is readable transaction
- err := d.DB.View(func(tx *bolt.Tx) error {
- // Get retrieves the value for a key in the bucket.
- // Returns a nil value if the key does not exist or if the key is a nested bucket.
- // The returned value is only valid for the life of the transaction.
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return errors.E(op, errors.EmptyKey)
- }
- b := tx.Bucket(d.bucket)
- if b == nil {
- return errors.E(op, errors.NoSuchBucket)
- }
- exist := b.Get([]byte(keys[i]))
- if exist != nil {
- m[keys[i]] = true
- }
- }
- return nil
- })
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- d.log.Debug("boltdb HAS method finished")
- return m, nil
-}
-
-// Get retrieves the value for a key in the bucket.
-// Returns a nil value if the key does not exist or if the key is a nested bucket.
-// The returned value is only valid for the life of the transaction.
-func (d *Driver) Get(key string) ([]byte, error) {
- const op = errors.Op("boltdb_driver_get")
- // to get cases like " "
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
-
- var val []byte
- err := d.DB.View(func(tx *bolt.Tx) error {
- b := tx.Bucket(d.bucket)
- if b == nil {
- return errors.E(op, errors.NoSuchBucket)
- }
- val = b.Get([]byte(key))
-
- // try to decode values
- if val != nil {
- buf := bytes.NewReader(val)
- decoder := gob.NewDecoder(buf)
-
- var i string
- err := decoder.Decode(&i)
- if err != nil {
- // unsafe (w/o runes) convert
- return errors.E(op, err)
- }
-
- // set the value
- val = utils.AsBytes(i)
- }
- return nil
- })
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return val, nil
-}
-
-func (d *Driver) MGet(keys ...string) (map[string][]byte, error) {
- const op = errors.Op("boltdb_driver_mget")
- // defense
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- }
-
- m := make(map[string][]byte, len(keys))
-
- err := d.DB.View(func(tx *bolt.Tx) error {
- b := tx.Bucket(d.bucket)
- if b == nil {
- return errors.E(op, errors.NoSuchBucket)
- }
-
- buf := new(bytes.Buffer)
- var out []byte
- buf.Grow(100)
- for i := range keys {
- value := b.Get([]byte(keys[i]))
- buf.Write(value)
- // allocate enough space
- dec := gob.NewDecoder(buf)
- if value != nil {
- err := dec.Decode(&out)
- if err != nil {
- return errors.E(op, err)
- }
- m[keys[i]] = out
- buf.Reset()
- out = nil
- }
- }
-
- return nil
- })
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return m, nil
-}
-
-// Set puts the K/V to the bolt
-func (d *Driver) Set(items ...*kvv1.Item) error {
- const op = errors.Op("boltdb_driver_set")
- if items == nil {
- return errors.E(op, errors.NoKeys)
- }
-
- // start writable transaction
- tx, err := d.DB.Begin(true)
- if err != nil {
- return errors.E(op, err)
- }
- defer func() {
- err = tx.Commit()
- if err != nil {
- errRb := tx.Rollback()
- if errRb != nil {
- d.log.Error("during the commit, Rollback error occurred", "commit error", err, "rollback error", errRb)
- }
- }
- }()
-
- b := tx.Bucket(d.bucket)
- // use access by index to avoid copying
- for i := range items {
- // performance note: pass a prepared bytes slice with initial cap
- // we can't move buf and gob out of loop, because we need to clear both from data
- // but gob will contain (w/o re-init) the past data
- buf := new(bytes.Buffer)
- encoder := gob.NewEncoder(buf)
- if errors.Is(errors.EmptyItem, err) {
- return errors.E(op, errors.EmptyItem)
- }
-
- // Encode value
- err = encoder.Encode(&items[i].Value)
- if err != nil {
- return errors.E(op, err)
- }
- // buf.Bytes will copy the underlying slice. Take a look in case of performance problems
- err = b.Put([]byte(items[i].Key), buf.Bytes())
- if err != nil {
- return errors.E(op, err)
- }
-
- // if there are no errors, and TTL > 0, we put the key with timeout to the hashmap, for future check
- // we do not need mutex here, since we use sync.Map
- if items[i].Timeout != "" {
- // check correctness of provided TTL
- _, err := time.Parse(time.RFC3339, items[i].Timeout)
- if err != nil {
- return errors.E(op, err)
- }
- // Store key TTL in the separate map
- d.gc.Store(items[i].Key, items[i].Timeout)
- }
-
- buf.Reset()
- }
-
- return nil
-}
-
-// Delete all keys from DB
-func (d *Driver) Delete(keys ...string) error {
- const op = errors.Op("boltdb_driver_delete")
- if keys == nil {
- return errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for _, key := range keys {
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return errors.E(op, errors.EmptyKey)
- }
- }
-
- // start writable transaction
- tx, err := d.DB.Begin(true)
- if err != nil {
- return errors.E(op, err)
- }
-
- defer func() {
- err = tx.Commit()
- if err != nil {
- errRb := tx.Rollback()
- if errRb != nil {
- d.log.Error("during the commit, Rollback error occurred", "commit error", err, "rollback error", errRb)
- }
- }
- }()
-
- b := tx.Bucket(d.bucket)
- if b == nil {
- return errors.E(op, errors.NoSuchBucket)
- }
-
- for _, key := range keys {
- err = b.Delete([]byte(key))
- if err != nil {
- return errors.E(op, err)
- }
- }
-
- return nil
-}
-
-// MExpire sets the expiration time to the key
-// If key already has the expiration time, it will be overwritten
-func (d *Driver) MExpire(items ...*kvv1.Item) error {
- const op = errors.Op("boltdb_driver_mexpire")
- for i := range items {
- if items[i].Timeout == "" || strings.TrimSpace(items[i].Key) == "" {
- return errors.E(op, errors.Str("should set timeout and at least one key"))
- }
-
- // verify provided TTL
- _, err := time.Parse(time.RFC3339, items[i].Timeout)
- if err != nil {
- return errors.E(op, err)
- }
-
- d.gc.Store(items[i].Key, items[i].Timeout)
- }
- return nil
-}
-
-func (d *Driver) TTL(keys ...string) (map[string]string, error) {
- const op = errors.Op("boltdb_driver_ttl")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- }
-
- m := make(map[string]string, len(keys))
-
- for i := range keys {
- if item, ok := d.gc.Load(keys[i]); ok {
- // a little bit dangerous operation, but user can't store value other that kv.Item.TTL --> int64
- m[keys[i]] = item.(string)
- }
- }
- return m, nil
-}
-
-func (d *Driver) Clear() error {
- err := d.DB.Update(func(tx *bolt.Tx) error {
- err := tx.DeleteBucket(d.bucket)
- if err != nil {
- d.log.Error("boltdb delete bucket", "error", err)
- return err
- }
-
- _, err = tx.CreateBucket(d.bucket)
- if err != nil {
- d.log.Error("boltdb create bucket", "error", err)
- return err
- }
-
- return nil
- })
-
- if err != nil {
- d.log.Error("clear transaction failed", "error", err)
- return err
- }
-
- d.clearMu.Lock()
- d.gc = sync.Map{}
- d.clearMu.Unlock()
-
- return nil
-}
-
-func (d *Driver) Stop() {
- d.stop <- struct{}{}
-}
-
-// ========================= PRIVATE =================================
-
-func (d *Driver) startGCLoop() { //nolint:gocognit
- go func() {
- t := time.NewTicker(d.timeout)
- defer t.Stop()
- for {
- select {
- case <-t.C:
- d.clearMu.RLock()
-
- // calculate current time before loop started to be fair
- now := time.Now()
- d.gc.Range(func(key, value interface{}) bool {
- const op = errors.Op("boltdb_plugin_gc")
- k := key.(string)
- v, err := time.Parse(time.RFC3339, value.(string))
- if err != nil {
- return false
- }
-
- if now.After(v) {
- // time expired
- d.gc.Delete(k)
- d.log.Debug("key deleted", "key", k)
- err := d.DB.Update(func(tx *bolt.Tx) error {
- b := tx.Bucket(d.bucket)
- if b == nil {
- return errors.E(op, errors.NoSuchBucket)
- }
- err := b.Delete(utils.AsBytes(k))
- if err != nil {
- return errors.E(op, err)
- }
- return nil
- })
- if err != nil {
- d.log.Error("error during the gc phase of update", "error", err)
- return false
- }
- }
- return true
- })
-
- d.clearMu.RUnlock()
- case <-d.stop:
- err := d.DB.Close()
- if err != nil {
- d.log.Error("error")
- }
- return
- }
- }
- }()
-}
diff --git a/plugins/boltdb/doc/boltjobs.drawio b/plugins/boltdb/doc/boltjobs.drawio
deleted file mode 100644
index 7d1f3531..00000000
--- a/plugins/boltdb/doc/boltjobs.drawio
+++ /dev/null
@@ -1 +0,0 @@
-<mxfile host="Electron" modified="2021-08-31T09:34:11.357Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/91.0.4472.164 Electron/13.2.3 Safari/537.36" etag="KiNZAPNeIcd5kV3EE5lF" version="14.6.13" type="device"><diagram id="NuJwivb--D1hymDgb9NQ" name="Page-1">7V1bc5s4GP01nmkfkgHE9TF2km13up20me1uH2WQbRJsuYATe3/9ijtICiHmIrvBnWmNEAjrfNejT3QCZuv9Hz7crv7CDvImiuTsJ+B6oigKABr5J2o5JC0yUJWkZem7TtpWNNy7/6G0UUpbd66DgkrHEGMvdLfVRhtvNsgOK23Q9/FztdsCe9VRt3CJmIZ7G3ps6z+uE67SVl2SihOfkLtcZUMr2Zk1zHqnDcEKOvi51ARuJmDmYxwm39b7GfKi6csmJrnu9oWz+ZP5aBM2ucDcf0ZP8pX6w3iw/K8/fijw1/VFikYQHrJfjBwyAekh9sMVXuIN9G6K1qmPdxsHRXeVyFHR5wvGW9Iok8YHFIaHFE24CzFpWoVrLz1LHtg//Btdf6llhz/T28UH1/vK0SE9WuBNeAvXrhc1zGBgQ8eF5OlnRO7S0+mYMiDHyU+Lfs+LM5aJFPSXKKyZJjnHi4g6wmtEHotc5yMPhu5T9f4wFbll3i+/9A67ZGRFSvVDV1PROORiVb1F8lzpVWVoX7uRSt0owDvfRsyNyJfS7ymaYsl5gxRl2jqwGO3dsCRF5Ohn6UwhQ9HBoSxQpyR6CTKviV4DEVVbimhFJt4sAMl9n6C3S0e6+/v+E2n5fjdjRKMK/PPKDdH9FsaT8EzcSBXkN078wvW8GfawHw8F5tA2nag9CH38iEpnFKCqmpND9YT8EO3rwWInN71AM6vqp2R6/FxyGZkbWJW8Ba2mneFhiNRHuaSNhW7y9VGcXoGGeqWJ1CvzXHAs7GrFqhZG9uTsalP8hdpVa8RfNP66SPwB41enJPm5nrb0qVUnuSAf0+Q5SaADCzjHwtTKp+pVl6qZGutSZY5LBX25VFlmJv1EdVGcTrW1lfzsxqDCK7VhmtRVdqOywe0uWJGW6c5+RGG3uqhFf3i6qMefFL+yjsYfITqqaBQwoGHcSwPYmZIqQh1mNRGVapX0pUQ050dqHGZNwmM7aG7OubacfG5vX5eTDs2Bfg4hlgxOR2Qachfy5ExiLFk5hyBL1hgDP8PbaPwHPOcKxxc4R14VUOi5yw35bpNJQkTlppFldW3oXaUn1q7jJLKDAvc/OI/vF8GzjfxW/Ju06US75ul4E43lWPKUFE/HmuRMdBnKGp140e4TkbOUdNJa+vYLWbIqPuRCoXwDXiwC1A9rqYvVfKO56tMS0RONKUL1ZaOXoJEkB1QGMXDUmE0AGzZ6bhCiDTESivRBltbBx24jyAXSbZsXATiGNZeOdgitIkUmhOdFiuqQkaLMRvXyZTwHheWXiO6Sv1aI/P15822HdigL+XWPPOB07ldw03/tosW8eAIvgngGr0gHGWz38TRm58m3ZfSvEg14jTwURiMsfLzOh4tE5YX0gqAQ8n2PhxZhO88TmasghZ4RoQ3eIDHSQ5EBwGSlJ5eUQfh1WSgx+5ag8Qw9h9HUc1jDeA59aM9hsJ7j010k7th/RH4QzSvGXkuvQckFE79SbgXJjoYMnluxdANAvamItDIEskQtdGuG6JU2RSgtKF0qw6x9vzkd6cYUWA1NQamgRkACmeVCp+8NfmcReCFnHWihRjCL9IZcsisZYByBFH9OVDrEcowW49G/o2CLNwEa1os7EJkLbnKo2yaaL8R4cV28FxdaMCOVdLf5QnvLdYPBdDQD8rRJ4OwpSyr6FdqPw6rnwrQRn7uZm5qqNbatrdTTtKraaUrCtZOl0m58n0xNc2qkC1q+KTnSBXXfHC4rq4xI4TI41JrGQYvm2bsLhQRnREdFQsq5GNOhypbiS698Hx5KHVKdaE6dWDq1J4G6QDFr+5MvySN0yq8obFXV1dCW3tGQ6ag8S28qc6ALolPEm3ogOJU+jk4xWlmPU0ikFLWhXWmdZh9nWHJ3lUmqVW9YZBnUXtCPZQGCV5TbkwC/t+y2ZQmPlF2qTM6iN+pRF1j1/XsSXbEZ8HslsBrLLhBKYCmcGlgfh9h+n8tQ8smlyOrZVKe32SlyatqrNVReVejyFFveeE3GjcbvoYC9PcM8QKF642Xj3sqPsui0Uh0WxQDzmHQsVx4dVQr0+/JdJDyqYsmxvQpvZ1B/hBdLT+ZFXX/GdWSV0q5KJdll3seN6jocMquXI8xlFc2CYJWFGRiDwnxCe0veT4AMGgfIpkgfq57hAmI1/jJaB2C3t5Jknqh0iKHDKXrVMl8hraj+hqZRgtlH4s+mdt/Rr8hDtYwL37r2CR0D8tc+VV2T1GEyO5pntIS/yiN7YdY5WZauCXERlsVsaFmEvh8ke0o2txtVV5Y04bqritTd41gZSndbx4wCdDfDs/eNx0dFBQZd/SbVRwV0f1Wu7w/q+/cTRagsn5GRTD1vd2tdMtUD36Q14ZsG3e6mM/B83pDjb0mcJ817IAJbVzgMQAQackMX0d8bK7gePCGPHl4hjxi83iVRRJUQ8dZilEGJIpUl3JOtpSsUc7tOahh9uFlGmH6A8T0J2kH82OEqsecJ4nGvj+Udp6XNowlnjPZb1ydqmt+jLXs83EbSYWVFpdbtdB6paA0aIdao/6j4tWAa1EKAxdsVzHOy/Sk+u80jUdDEkEeaHan7V/w8iZ5hmtkCot3SIzpUFTcOn0a4ixfCqVW4uekdV3l7w1tjg152Gf7ssKLeLxblbzHn34WvpsqDAcf+8qKv/hBkX1zM0m0jgiUEqWU5VTiCCoMguxVzRLBA0KJiII2TAg2LIFuOz9JmI4IFgnL+4tMsjOVs+RkWQnYNia3jHyEsQUjvvODt2hoWQjZpHSGsh5AiHgzOUuGwELIk3whhvSEFp6aF7Kt/2C0lI4SleJR6NRiX/RsUQZbQYUuDRgQLBOktoDonrR8WQZbFGXWwVgfp/Uqic8LMJowINq6doBdRLNEQssTMaEbrIKTfsMplwwdFkCVm2JdmjggWCOpKA4K7IwjJYfEfTyY1IMV/4Alu/gc=</diagram></mxfile> \ No newline at end of file
diff --git a/plugins/boltdb/doc/job_lifecycle.md b/plugins/boltdb/doc/job_lifecycle.md
deleted file mode 100644
index 1424e586..00000000
--- a/plugins/boltdb/doc/job_lifecycle.md
+++ /dev/null
@@ -1,9 +0,0 @@
-### Job lifecycle
-
-There are several boltdb buckets:
-
-1. `PushBucket` - used for pushed jobs via RPC.
-2. `InQueueBucket` - when the job consumed from the `PushBucket`, in the same transaction, it copied into the priority queue and
-get into the `InQueueBucket` waiting to acknowledgement.
-3. `DelayBucket` - used for delayed jobs. RFC3339 used as a timestamp to track delay expiration.
-
diff --git a/plugins/boltdb/plugin.go b/plugins/boltdb/plugin.go
deleted file mode 100644
index ad98cf3c..00000000
--- a/plugins/boltdb/plugin.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package boltdb
-
-import (
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/jobs"
- "github.com/spiral/roadrunner/v2/common/kv"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- "github.com/spiral/roadrunner/v2/plugins/boltdb/boltjobs"
- "github.com/spiral/roadrunner/v2/plugins/boltdb/boltkv"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const (
- PluginName string = "boltdb"
-)
-
-// Plugin BoltDB K/V storage.
-type Plugin struct {
- cfg config.Configurer
- // logger
- log logger.Logger
-}
-
-func (p *Plugin) Init(log logger.Logger, cfg config.Configurer) error {
- p.log = log
- p.cfg = cfg
- return nil
-}
-
-// Serve is noop here
-func (p *Plugin) Serve() chan error {
- return make(chan error, 1)
-}
-
-func (p *Plugin) Stop() error {
- return nil
-}
-
-// Name returns plugin name
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (p *Plugin) Available() {}
-
-func (p *Plugin) KVConstruct(key string) (kv.Storage, error) {
- const op = errors.Op("boltdb_plugin_provide")
- st, err := boltkv.NewBoltDBDriver(p.log, key, p.cfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return st, nil
-}
-
-// JOBS bbolt implementation
-
-func (p *Plugin) JobsConstruct(configKey string, e events.Handler, queue priorityqueue.Queue) (jobs.Consumer, error) {
- return boltjobs.NewBoltDBJobs(configKey, p.log, p.cfg, e, queue)
-}
-
-func (p *Plugin) FromPipeline(pipe *pipeline.Pipeline, e events.Handler, queue priorityqueue.Queue) (jobs.Consumer, error) {
- return boltjobs.FromPipeline(pipe, p.log, p.cfg, e, queue)
-}
diff --git a/plugins/broadcast/config.go b/plugins/broadcast/config.go
deleted file mode 100644
index 9531025b..00000000
--- a/plugins/broadcast/config.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package broadcast
-
-/*
-
-# Global redis config (priority - 2)
-default:
- # redis configuration here
-
-websockets: # <----- one of possible subscribers
- path: /ws
- broker: default # <------ broadcast broker to use --------------- |
- | match
-broadcast: # <-------- broadcast entry point plugin |
- default: # <----------------------------------------------------- |
- driver: redis
- # local redis config (priority - 1)
- test:
- driver: memory
-
-
-priority local -> global
-*/
-
-// Config ...
-type Config struct {
- Data map[string]interface{} `mapstructure:"broadcast"`
-}
diff --git a/plugins/broadcast/doc/broadcast_arch.drawio b/plugins/broadcast/doc/broadcast_arch.drawio
deleted file mode 100644
index fd5ff1f9..00000000
--- a/plugins/broadcast/doc/broadcast_arch.drawio
+++ /dev/null
@@ -1 +0,0 @@
-<mxfile host="Electron" modified="2021-06-18T09:34:25.915Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/89.0.4389.128 Electron/12.0.9 Safari/537.36" etag="THNfOcV33EQGG0gzo1UK" version="14.6.13" type="device"><diagram id="xG4Au6HO45p6fae_AhkE" name="Page-1">7V1bc6M4Fv41rk1vVVIg7o+Jk8l01fR2Np7e7n7a4iLbbDB4AMdJ//qVQGCQZBsHEMSTviRGIAznfj4dSRNlunq5j+318kvkwWACJO9lotxOALBkE/3EDa95AzAVJW9ZxL6Xt8m7hpn/C5JGibRufA8mtQvTKApSf11vdKMwhG5aa7PjONrWL5tHQf1b1/YCMg0z1w7Y1u++ly7Ja0iStDvxO/QXy5Q+s7KLq0lDsrS9aFtpUu4myjSOojT/tHqZwgBTryBM3u+3PWfLJ4thmDbpMFV1/fPNfXhp/FAuZ+Hz8kd0f6mr+W2e7WBDXpk8bfpa0CCONqEH8V2kiXKzXfopnK1tF5/dIq6jtmW6CtCRjD7O/SCYRkEUZ30Vz4bm3EXtSRpHT7ByRndN6MzRGfY9yKs9wziFL5Um8l73MFrBNH5Fl5CzikloTMRM1cjxtsazvG1ZZZdFGm0iJ4vy3jtKog+EmCcQVtP7Jexcw3+5hM3+4B5RmFba8z/dEFyW5DrFDYOluGpyKK7oWl8U186b4qBO8ctSoAckec/WY2iSU0KuSNrQFLcY+kIPOSxyGMXpMlpEoR3c7Vpv6hzYXfNHFK0J3f8H0/SVeF97k0Z1rsAXP/2Bu19p5OgnuRn+fPtSPXgtDkL0upVO+PBncT98sOuWHRX99vItiTaxCw+QpggP7HgB00PXEYZhwh0UgxgGduo/1yMBHkdJ14fIR89cio8BpCtZBaZmkJ81WdIkyvPkz03uQUlJ+VBvF5xCIoeSHFAVHbmh6FQFpyJHe0SHNh6mC11u6OGYGo4QRAhbEV91LGzXcWy/Vi5YY7FJ9suibmo16dNVKlakr5fbXW/Kh69XJKnV9ZqlUUqSU6RTlSkcxs653Tx+vb6dXs/+7NbLtRDU5t5M1ygSciI2WeU4M62vEFkd0iLJFXO082unGSRwqkWazwGf0Z7u6JreyiKBpu6va4PUSgYAo2OPd7efZ6jp4dvN7NtNt4o2h/oe+huWI3WkaCoVqJcRYlXRgEhFG9T1d6FoY9Iz5V3qmcLo2Ze7L18ff56TonE9mlBFM967ohlj0jT9XWoai/tdf/n3w0T5Df87I23T9aG1zWQojV49wKMA6Muy7vaz7Qe2g4iMXn7jJBsHffBiJANxwrAA0SGt09kO/EWIPruIahCR9AZTy3ft4JqcWPmelysxTPxf2RflDCT5GLqvdjPRbvG9kN4muQrLDKvCKIT9IOGaynKJj4T3xSVZGdIojhyykpqauHHZuOK5q6lxHNmeaydY+XysLnNsvbo0c54GTU/lmTkTOIp+0NeckCbrb4ze1d4USP9QoGN6cVyBtK4V6E04nEE50WL8cR/uZRlmq+v1I9fLsiUd6tAPsKaBYSXaaC7SAkRT7dy2v000DaUmCbJ5ouhQHXoSnUGjiXqKZTTNsUZnDYE1CpEzKQlSSay4T+To62VZO1VGqR49DRywIwcToAc4u1jXZFf/a4Orf25WiGE+Siiu0Vlp/YJ+ZnIg5e2XKRZTfE6tnMPpyiXJUPA5kqSU90SfFuS3vcKRVcAeVQI10lzvUhxlFztx5ZZ5S9FQBn/ZA5Av+s2hr0dta7ptydy1OxKRcwGcp8fpl13FUO8eprdZvnjxBF+zB4v9cPEJfZptnEdoe7U3rrwdZaSSpb3GH+OsSK1qWyJ083mQFYbhgHdPWNwh7nJCLEyN6imclL+s1KvGwkZvsfBH/cPREPe49RdUAMFYX42GgnqueZDZirDv0Eki9wmmLP7TJjd1ZM+bSzx9lCVDsToCd2RZoRFv44otSeKmp0pfFUmywRD5c+inFxhqK/3CJ4baZ4G2MUV5OuCgBWLhNhYVfYQuxFoMJKcRSHOmrOEgobIukjWlJX9PuUvVdzUYH3q7+yrIftx9AUHuC9BYuiJdYYDd0sjPRs7s1KRILqMnCsLfm+SwHUxKVnvIccCg1TvjjsSai3I/qCQ3EruSJUVVTWBopg7qUI9Kz4LoOS4r6FPxUV+3Ye6fnnBCJSUoTfKjMHNTk2x2z9xfnEXEJnO8kNDhBDDseFzNC4GGXkiue6GjdXctVBc0Vd3Ok6h2XAWMShW4hOc/F7DEdpf9YMyjxC0ql3B6lXBQ6CTrHDwhipoBJx6c25sgbXM7OwiiLfT+G8V+CfLssJh/1mCZN3/JOpuQV781us82OX77NnaniJ1zFGogM6RQM1lUi5M28op/e0NywMeo5tGw4LgRMkTFD6Z1tZvIohbfW+CCouMHFtcpkYarHWJbGilioTBqe0lj21Uc9/2FFxJV1K8qbHgBuDPUetNrFg6aIXHAwgRfoIs0EhF6momk7aFfK5gk9gIiIywFWIvfIRPoGE/hIQ1iYzzzw7jupY3V0Lgq/UzdajBIWc47FWVPLUZnv6bLLBF7CDaLLAVjsavx13IxmolS4OEBc4XNfv++gLksWRxjKRQxV9jR+g/EPFcfHlghFDJXwJCObOyQeeGhjrsyXZAroyFzyxoEMpeLVXgaY+aohwDQXPmohe9AmE1xcdkB0NwSXMxQ0GdI0NyTXAgBL2zTdE02+kqorKYT+3pLqJRhV8+o+aHxgeZK05l63adU7bjKW6OLgZLzVKg7vDyFSQpa3GxfNaY40FqQGaBBa0sbGrRWBp3gO3L/bTY1Av0Un58KWpui/TdbmDU20FqQXtOgtcmBAMSC1goLgM1g/Nzx/MVhgieTs0yg2OCpuPGH1dw/+ev4LDFRKTyNRtNi0bOZVFk0ro5Fs4hQq7UTbM/yuCtM2lCVFdCTUurDw9AqYAj9N4Khafhn8LptlV2h5wOFrkX4g4HQ6ogWqhsfCK02Tf5VUbgdDUKDOgYtm2ojj9YahVZOxKCJ69t7PVPEVbu+H8RaHXjZ2FHHbk0lv8h3BkasL2kL2Xcox8JrwhFr0fHdcEnWsNDUyBFqtSk41X2a1Y6rLGi0D6HOs6NprkLdotXyKTcbHJ0WpPK0Ny5nGx9Fp3vL6D4mxx91wcd9dT+r8ZyKTl/Kgp11QaARw9OCFJuGpy85K8CJhac1FhPrHp4eCAm7lAcf3NfPvipH5M4YmtHQ0o5rSUuNnbfw+DCt4237YptkbYdFWwnVXT1snMBPlhdkhsOnSiRU7dCpEkPZ06DB46ylG4rd1bpASn1dIIM3xGQIVeFBl1Y4SYXRwQOMffTe2I8OFQ41zYe0zmeYtdNSNh/aJJwwo1hua7MKrl085WgHff9hOzB4iBI/Qx6UWydK02g1YbHxbEmx2kpdmzTwQ6RUxVaTB3nQXJ0Mido8h7N7ocLRJr03bWKBmxkMvUk5WwtLToS/Gf0nZo7DhLMYj1AokNbgrQfMs3W9DUforKd6iKNn38Nsqa3ElzMpqg2fnum65zq1QRRoulRdF2y6MR7+9f3PG/mv6eNqMdV/RL/Pvg27FwS131rjqBIICSspZ3LUhXHpyymc4F5ntvRX/PRdo5YJlzXaGneXr/Nff9ApnvVByaZ4b0v5aryXsFD5kkUFRNxvx/1HY2aabuvYKnkdqRSALszMqUPOjBUy9GNjzvLhHq1HkQ9Rkb9dRJcYVlMPxAQWHOnYG2to9KxijbeaSl8QFt8MMATO8dpMAhc+IodNBloTdwlX9miDwFaMAQxjOJlUb7VDXMaMqHRoXFvvdWKdOXvx7bc/Q7loTmEftcUljSt+Xq0DuIKZ6cf59Szbt2oC8NDKw2wahYi4mwzW6NR4Nt1GrGMd5S0A0Nc+tFwGsasJkX3EkvzbjhH9LGwnjXPw+SLUdrIoVIUP6ISLQXRpXWIfXuHyWI1y7ARi/CpzgekS7vrh1rzuaFP6yGyp/8ZMJlUI42cxoDASWee4Ry7S2MXIJpfFA1csGG/KXoxa+rJziSN0kByYf3wOskhJPoKkYWWgn0IXnV7dSpOkq7w8VdJNC7+uSNyMHUCa3T3+544ThIX+biPRfPNQHAqgmMpd4vYYcirnkjV0/bmfeRV/52v6KGwVEq0xqa7atFqji8Epfqo78GzCt/gMqtD11F2WBUNejTGvLuzFyXuGGSplTo7sAWape0CBxh10st5ovxAZZxUyKk2ULrBN8lHc+SszMHY2JJvg1TWh94m1YLOyuv4Cvtg4p/xErdjcxh4dWSKiqci2Mk/0yLms6iwSB4rlYrteIoLPx/e4rrHRBpBvXHPUjXUCY7ZOLL5ebJu+19rQ4dHJHQz5cIdyT6I9HXqyZywk3dPeW43lr5WpKXeO3PkRkXN++DRmK0Hu8wnDq9qO93MMWpFQdIXfqZh3ddAbnAWspZu0M+dVc/Pgxt5grWIPwyreuITuU4Uv58kKpmTCZJ21JZITgI25fuKVyHHZ72f8EHamTOnST6rMmWbPh/M/KTvxinzWZAc/niXvaKejS5xAS6gaAd6CCekmxvEuqUvMx03KAm4cLa+L9awvYuhl3CNpes5iEiWfJQ8NqmpbNjgrKyhCa+SKTcRqQy+VgRYphDCH6HN9w5rnQNfeJFj5thnoH4X/wMHOUxhtJyyUb6el5yOADsqXFvCKzZZKedn62YM7uI/9bPtBxjxSTOnkcE42PJQ9mPNafsFd6G0yqxCFQRY0zvMqzOoXZxxLEuhdNVnY7xzFkAlfAWtJVN440xsGIdBhHOGdfXaRLYoml18iD+Ir/g8=</diagram></mxfile> \ No newline at end of file
diff --git a/plugins/broadcast/interface.go b/plugins/broadcast/interface.go
deleted file mode 100644
index eda3572f..00000000
--- a/plugins/broadcast/interface.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package broadcast
-
-import "github.com/spiral/roadrunner/v2/common/pubsub"
-
-type Broadcaster interface {
- GetDriver(key string) (pubsub.SubReader, error)
-}
diff --git a/plugins/broadcast/plugin.go b/plugins/broadcast/plugin.go
deleted file mode 100644
index 40263eaa..00000000
--- a/plugins/broadcast/plugin.go
+++ /dev/null
@@ -1,192 +0,0 @@
-package broadcast
-
-import (
- "fmt"
- "sync"
-
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const (
- PluginName string = "broadcast"
- // driver is the mandatory field which should present in every storage
- driver string = "driver"
-
- // every driver should have config section for the local configuration
- conf string = "config"
-)
-
-type Plugin struct {
- sync.RWMutex
-
- cfg *Config
- cfgPlugin config.Configurer
- log logger.Logger
- // publishers implement Publisher interface
- // and able to receive a payload
- publishers map[string]pubsub.PubSub
- constructors map[string]pubsub.Constructor
-}
-
-func (p *Plugin) Init(cfg config.Configurer, log logger.Logger) error {
- const op = errors.Op("broadcast_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
- p.cfg = &Config{}
- // unmarshal config section
- err := cfg.UnmarshalKey(PluginName, &p.cfg.Data)
- if err != nil {
- return errors.E(op, err)
- }
-
- p.publishers = make(map[string]pubsub.PubSub)
- p.constructors = make(map[string]pubsub.Constructor)
-
- p.log = log
- p.cfgPlugin = cfg
- return nil
-}
-
-func (p *Plugin) Serve() chan error {
- return make(chan error, 1)
-}
-
-func (p *Plugin) Stop() error {
- return nil
-}
-
-func (p *Plugin) Collects() []interface{} {
- return []interface{}{
- p.CollectPublishers,
- }
-}
-
-// CollectPublishers collect all plugins who implement pubsub.Publisher interface
-func (p *Plugin) CollectPublishers(name endure.Named, constructor pubsub.Constructor) {
- // key redis, value - interface
- p.constructors[name.Name()] = constructor
-}
-
-// Publish is an entry point to the websocket PUBSUB
-func (p *Plugin) Publish(m *pubsub.Message) error {
- p.Lock()
- defer p.Unlock()
-
- const op = errors.Op("broadcast_plugin_publish")
-
- // check if any publisher registered
- if len(p.publishers) > 0 {
- for j := range p.publishers {
- err := p.publishers[j].Publish(m)
- if err != nil {
- return errors.E(op, err)
- }
- }
- return nil
- } else {
- p.log.Warn("no publishers registered")
- }
-
- return nil
-}
-
-func (p *Plugin) PublishAsync(m *pubsub.Message) {
- // TODO(rustatian) channel here?
- go func() {
- p.Lock()
- defer p.Unlock()
- // check if any publisher registered
- if len(p.publishers) > 0 {
- for j := range p.publishers {
- err := p.publishers[j].Publish(m)
- if err != nil {
- p.log.Error("publishAsync", "error", err)
- // continue publishing to the other registered publishers
- continue
- }
- }
- } else {
- p.log.Warn("no publishers registered")
- }
- }()
-}
-
-func (p *Plugin) GetDriver(key string) (pubsub.SubReader, error) {
- const op = errors.Op("broadcast_plugin_get_driver")
-
- // choose a driver
- if val, ok := p.cfg.Data[key]; ok {
- // check type of the v
- // should be a map[string]interface{}
- switch t := val.(type) {
- // correct type
- case map[string]interface{}:
- if _, ok := t[driver]; !ok {
- panic(errors.E(op, errors.Errorf("could not find mandatory driver field in the %s storage", val)))
- }
- default:
- return nil, errors.E(op, errors.Str("wrong type detected in the configuration, please, check yaml indentation"))
- }
-
- // config key for the particular sub-driver broadcast.memcached.config
- configKey := fmt.Sprintf("%s.%s.%s", PluginName, key, conf)
-
- drName := val.(map[string]interface{})[driver]
-
- // driver name should be a string
- if drStr, ok := drName.(string); ok {
- if _, ok := p.constructors[drStr]; !ok {
- return nil, errors.E(op, errors.Errorf("no drivers with the requested name registered, registered: %s, requested: %s", p.publishers, drStr))
- }
-
- switch {
- // try local config first
- case p.cfgPlugin.Has(configKey):
- // we found a local configuration
- ps, err := p.constructors[drStr].PSConstruct(configKey)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // save the initialized publisher channel
- // for the in-memory, register new publishers
- p.publishers[configKey] = ps
-
- return ps, nil
- case p.cfgPlugin.Has(key):
- // try global driver section after local
- ps, err := p.constructors[drStr].PSConstruct(key)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // save the initialized publisher channel
- // for the in-memory, register new publishers
- p.publishers[configKey] = ps
-
- return ps, nil
- default:
- p.log.Error("can't find local or global configuration, this section will be skipped", "local: ", configKey, "global: ", key)
- }
- }
- }
- return nil, errors.E(op, errors.Str("could not find driver by provided key"))
-}
-
-func (p *Plugin) RPC() interface{} {
- return &rpc{
- plugin: p,
- log: p.log,
- }
-}
-
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-func (p *Plugin) Available() {}
diff --git a/plugins/broadcast/rpc.go b/plugins/broadcast/rpc.go
deleted file mode 100644
index 475076a0..00000000
--- a/plugins/broadcast/rpc.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package broadcast
-
-import (
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- websocketsv1 "github.com/spiral/roadrunner/v2/proto/websockets/v1beta"
-)
-
-// rpc collectors struct
-type rpc struct {
- plugin *Plugin
- log logger.Logger
-}
-
-// Publish ... msg is a proto decoded payload
-// see: root/proto
-func (r *rpc) Publish(in *websocketsv1.Request, out *websocketsv1.Response) error {
- const op = errors.Op("broadcast_publish")
-
- // just return in case of nil message
- if in == nil {
- out.Ok = false
- return nil
- }
-
- r.log.Debug("message published", "msg", in.String())
- msgLen := len(in.GetMessages())
-
- for i := 0; i < msgLen; i++ {
- for j := 0; j < len(in.GetMessages()[i].GetTopics()); j++ {
- if in.GetMessages()[i].GetTopics()[j] == "" {
- r.log.Warn("message with empty topic, skipping")
- // skip empty topics
- continue
- }
-
- tmp := &pubsub.Message{
- Topic: in.GetMessages()[i].GetTopics()[j],
- Payload: in.GetMessages()[i].GetPayload(),
- }
-
- err := r.plugin.Publish(tmp)
- if err != nil {
- out.Ok = false
- return errors.E(op, err)
- }
- }
- }
-
- out.Ok = true
- return nil
-}
-
-// PublishAsync ...
-// see: root/proto
-func (r *rpc) PublishAsync(in *websocketsv1.Request, out *websocketsv1.Response) error {
- // just return in case of nil message
- if in == nil {
- out.Ok = false
- return nil
- }
-
- r.log.Debug("message published", "msg", in.GetMessages())
-
- msgLen := len(in.GetMessages())
-
- for i := 0; i < msgLen; i++ {
- for j := 0; j < len(in.GetMessages()[i].GetTopics()); j++ {
- if in.GetMessages()[i].GetTopics()[j] == "" {
- r.log.Warn("message with empty topic, skipping")
- // skip empty topics
- continue
- }
-
- tmp := &pubsub.Message{
- Topic: in.GetMessages()[i].GetTopics()[j],
- Payload: in.GetMessages()[i].GetPayload(),
- }
-
- r.plugin.PublishAsync(tmp)
- }
- }
-
- out.Ok = true
- return nil
-}
diff --git a/plugins/config/config.go b/plugins/config/config.go
deleted file mode 100644
index b5807921..00000000
--- a/plugins/config/config.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package config
-
-import "time"
-
-// General is the part of the config plugin which contains general for the whole RR2 parameters
-// For example - http timeouts, headers sizes etc and also graceful shutdown timeout should be the same across whole application
-type General struct {
- // GracefulTimeout for the temporal and http
- GracefulTimeout time.Duration
-}
diff --git a/plugins/config/interface.go b/plugins/config/interface.go
deleted file mode 100644
index b3854e09..00000000
--- a/plugins/config/interface.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package config
-
-type Configurer interface {
- // UnmarshalKey takes a single key and unmarshals it into a Struct.
- //
- // func (h *HttpService) Init(cp config.Configurer) error {
- // h.config := &HttpConfig{}
- // if err := configProvider.UnmarshalKey("http", h.config); err != nil {
- // return err
- // }
- // }
- UnmarshalKey(name string, out interface{}) error
-
- // Unmarshal unmarshal the config into a Struct. Make sure that the tags
- // on the fields of the structure are properly set.
- Unmarshal(out interface{}) error
-
- // Get used to get config section
- Get(name string) interface{}
-
- // Overwrite used to overwrite particular values in the unmarshalled config
- Overwrite(values map[string]interface{}) error
-
- // Has checks if config section exists.
- Has(name string) bool
-
- // GetCommonConfig returns General section. Read-only
- GetCommonConfig() *General
-}
diff --git a/plugins/config/plugin.go b/plugins/config/plugin.go
deleted file mode 100755
index 918381c4..00000000
--- a/plugins/config/plugin.go
+++ /dev/null
@@ -1,174 +0,0 @@
-package config
-
-import (
- "bytes"
- "fmt"
- "os"
- "strings"
-
- "github.com/spf13/viper"
- "github.com/spiral/errors"
-)
-
-const PluginName string = "config"
-
-type Viper struct {
- viper *viper.Viper
- Path string
- Prefix string
- Type string
- ReadInCfg []byte
- // user defined Flags in the form of <option>.<key> = <value>
- // which overwrites initial config key
- Flags []string
-
- CommonConfig *General
-}
-
-// Init config provider.
-func (v *Viper) Init() error {
- const op = errors.Op("config_plugin_init")
- v.viper = viper.New()
- // If user provided []byte data with config, read it and ignore Path and Prefix
- if v.ReadInCfg != nil && v.Type != "" {
- v.viper.SetConfigType("yaml")
- return v.viper.ReadConfig(bytes.NewBuffer(v.ReadInCfg))
- }
-
- // read in environment variables that match
- v.viper.AutomaticEnv()
- if v.Prefix == "" {
- return errors.E(op, errors.Str("prefix should be set"))
- }
-
- v.viper.SetEnvPrefix(v.Prefix)
- if v.Path == "" {
- return errors.E(op, errors.Str("path should be set"))
- }
-
- v.viper.SetConfigFile(v.Path)
- v.viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
-
- err := v.viper.ReadInConfig()
- if err != nil {
- return errors.E(op, err)
- }
-
- // automatically inject ENV variables using ${ENV} pattern
- for _, key := range v.viper.AllKeys() {
- val := v.viper.Get(key)
- v.viper.Set(key, parseEnv(val))
- }
-
- // override config Flags
- if len(v.Flags) > 0 {
- for _, f := range v.Flags {
- key, val, err := parseFlag(f)
- if err != nil {
- return errors.E(op, err)
- }
-
- v.viper.Set(key, val)
- }
- }
-
- return nil
-}
-
-// Overwrite overwrites existing config with provided values
-func (v *Viper) Overwrite(values map[string]interface{}) error {
- if len(values) != 0 {
- for key, value := range values {
- v.viper.Set(key, value)
- }
- }
-
- return nil
-}
-
-// UnmarshalKey reads configuration section into configuration object.
-func (v *Viper) UnmarshalKey(name string, out interface{}) error {
- const op = errors.Op("config_plugin_unmarshal_key")
- err := v.viper.UnmarshalKey(name, &out)
- if err != nil {
- return errors.E(op, err)
- }
- return nil
-}
-
-func (v *Viper) Unmarshal(out interface{}) error {
- const op = errors.Op("config_plugin_unmarshal")
- err := v.viper.Unmarshal(&out)
- if err != nil {
- return errors.E(op, err)
- }
- return nil
-}
-
-// Get raw config in a form of config section.
-func (v *Viper) Get(name string) interface{} {
- return v.viper.Get(name)
-}
-
-// Has checks if config section exists.
-func (v *Viper) Has(name string) bool {
- return v.viper.IsSet(name)
-}
-
-// GetCommonConfig Returns common config parameters
-func (v *Viper) GetCommonConfig() *General {
- return v.CommonConfig
-}
-
-func (v *Viper) Serve() chan error {
- return make(chan error, 1)
-}
-
-func (v *Viper) Stop() error {
- return nil
-}
-
-// Name returns user-friendly plugin name
-func (v *Viper) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (v *Viper) Available() {}
-
-func parseFlag(flag string) (string, string, error) {
- const op = errors.Op("parse_flag")
- if !strings.Contains(flag, "=") {
- return "", "", errors.E(op, errors.Errorf("invalid flag `%s`", flag))
- }
-
- parts := strings.SplitN(strings.TrimLeft(flag, " \"'`"), "=", 2)
-
- return strings.Trim(parts[0], " \n\t"), parseValue(strings.Trim(parts[1], " \n\t")), nil
-}
-
-func parseValue(value string) string {
- escape := []rune(value)[0]
-
- if escape == '"' || escape == '\'' || escape == '`' {
- value = strings.Trim(value, string(escape))
- value = strings.ReplaceAll(value, fmt.Sprintf("\\%s", string(escape)), string(escape))
- }
-
- return value
-}
-
-func parseEnv(value interface{}) interface{} {
- str, ok := value.(string)
- if !ok || len(str) <= 3 {
- return value
- }
-
- if str[0:2] == "${" && str[len(str)-1:] == "}" {
- if v, ok := os.LookupEnv(str[2 : len(str)-1]); ok {
- return v
- }
- }
-
- return str
-}
diff --git a/plugins/gzip/plugin.go b/plugins/gzip/plugin.go
deleted file mode 100644
index 05f1eb63..00000000
--- a/plugins/gzip/plugin.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package gzip
-
-import (
- "net/http"
-
- "github.com/klauspost/compress/gzhttp"
-)
-
-const PluginName = "gzip"
-
-type Plugin struct{}
-
-func (g *Plugin) Init() error {
- return nil
-}
-
-func (g *Plugin) Middleware(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- gzhttp.GzipHandler(next).ServeHTTP(w, r)
- })
-}
-
-// Available interface implementation
-func (g *Plugin) Available() {}
-
-func (g *Plugin) Name() string {
- return PluginName
-}
diff --git a/plugins/headers/config.go b/plugins/headers/config.go
deleted file mode 100644
index 688b4764..00000000
--- a/plugins/headers/config.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package headers
-
-// Config declares headers service configuration.
-type Config struct {
- Headers *struct {
- // CORS settings.
- CORS *CORSConfig
-
- // Request headers to add to every payload send to PHP.
- Request map[string]string
-
- // Response headers to add to every payload generated by PHP.
- Response map[string]string
- }
-}
-
-// CORSConfig headers configuration.
-type CORSConfig struct {
- // AllowedOrigin: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
- AllowedOrigin string `mapstructure:"allowed_origin"`
-
- // AllowedHeaders: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
- AllowedHeaders string `mapstructure:"allowed_headers"`
-
- // AllowedMethods: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
- AllowedMethods string `mapstructure:"allowed_methods"`
-
- // AllowCredentials https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
- AllowCredentials *bool `mapstructure:"allow_credentials"`
-
- // ExposeHeaders: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
- ExposedHeaders string `mapstructure:"exposed_headers"`
-
- // MaxAge of CORS headers in seconds/
- MaxAge int `mapstructure:"max_age"`
-}
diff --git a/plugins/headers/plugin.go b/plugins/headers/plugin.go
deleted file mode 100644
index 19c444df..00000000
--- a/plugins/headers/plugin.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package headers
-
-import (
- "net/http"
- "strconv"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
-)
-
-// PluginName contains default service name.
-const PluginName = "headers"
-const RootPluginName = "http"
-
-// Plugin serves headers files. Potentially convert into middleware?
-type Plugin struct {
- // server configuration (location, forbidden files and etc)
- cfg *Config
-}
-
-// Init must return configure service and return true if service hasStatus enabled. Must return error in case of
-// misconfiguration. Services must not be used without proper configuration pushed first.
-func (s *Plugin) Init(cfg config.Configurer) error {
- const op = errors.Op("headers_plugin_init")
- if !cfg.Has(RootPluginName) {
- return errors.E(op, errors.Disabled)
- }
- err := cfg.UnmarshalKey(RootPluginName, &s.cfg)
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
-
- if s.cfg.Headers == nil {
- return errors.E(op, errors.Disabled)
- }
-
- return nil
-}
-
-// Middleware is HTTP plugin middleware to serve headers
-func (s *Plugin) Middleware(next http.Handler) http.Handler {
- // Define the http.HandlerFunc
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if s.cfg.Headers.Request != nil {
- for k, v := range s.cfg.Headers.Request {
- r.Header.Add(k, v)
- }
- }
-
- if s.cfg.Headers.Response != nil {
- for k, v := range s.cfg.Headers.Response {
- w.Header().Set(k, v)
- }
- }
-
- if s.cfg.Headers.CORS != nil {
- if r.Method == http.MethodOptions {
- s.preflightRequest(w)
- return
- }
- s.corsHeaders(w)
- }
-
- next.ServeHTTP(w, r)
- })
-}
-
-func (s *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (s *Plugin) Available() {}
-
-// configure OPTIONS response
-func (s *Plugin) preflightRequest(w http.ResponseWriter) {
- headers := w.Header()
-
- headers.Add("Vary", "Origin")
- headers.Add("Vary", "Access-Control-Request-Method")
- headers.Add("Vary", "Access-Control-Request-Headers")
-
- if s.cfg.Headers.CORS.AllowedOrigin != "" {
- headers.Set("Access-Control-Allow-Origin", s.cfg.Headers.CORS.AllowedOrigin)
- }
-
- if s.cfg.Headers.CORS.AllowedHeaders != "" {
- headers.Set("Access-Control-Allow-Headers", s.cfg.Headers.CORS.AllowedHeaders)
- }
-
- if s.cfg.Headers.CORS.AllowedMethods != "" {
- headers.Set("Access-Control-Allow-Methods", s.cfg.Headers.CORS.AllowedMethods)
- }
-
- if s.cfg.Headers.CORS.AllowCredentials != nil {
- headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*s.cfg.Headers.CORS.AllowCredentials))
- }
-
- if s.cfg.Headers.CORS.MaxAge > 0 {
- headers.Set("Access-Control-Max-Age", strconv.Itoa(s.cfg.Headers.CORS.MaxAge))
- }
-
- w.WriteHeader(http.StatusOK)
-}
-
-// configure CORS headers
-func (s *Plugin) corsHeaders(w http.ResponseWriter) {
- headers := w.Header()
-
- headers.Add("Vary", "Origin")
-
- if s.cfg.Headers.CORS.AllowedOrigin != "" {
- headers.Set("Access-Control-Allow-Origin", s.cfg.Headers.CORS.AllowedOrigin)
- }
-
- if s.cfg.Headers.CORS.AllowedHeaders != "" {
- headers.Set("Access-Control-Allow-Headers", s.cfg.Headers.CORS.AllowedHeaders)
- }
-
- if s.cfg.Headers.CORS.ExposedHeaders != "" {
- headers.Set("Access-Control-Expose-Headers", s.cfg.Headers.CORS.ExposedHeaders)
- }
-
- if s.cfg.Headers.CORS.AllowCredentials != nil {
- headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*s.cfg.Headers.CORS.AllowCredentials))
- }
-}
diff --git a/plugins/http/attributes/attributes.go b/plugins/http/attributes/attributes.go
deleted file mode 100644
index 243b6c78..00000000
--- a/plugins/http/attributes/attributes.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package attributes
-
-import (
- "context"
- "errors"
- "net/http"
-)
-
-// contextKey is a value for use with context.WithValue. It's used as
-// a pointer fits an interface{} without allocation.
-type contextKey struct {
- name string
-}
-
-func (k *contextKey) String() string { return k.name }
-
-var (
- // PsrContextKey is a context key. It can be used in the http attributes
- PsrContextKey = &contextKey{"psr_attributes"}
-)
-
-type attrs map[string]interface{}
-
-func (v attrs) get(key string) interface{} {
- if v == nil {
- return ""
- }
-
- return v[key]
-}
-
-func (v attrs) set(key string, value interface{}) {
- v[key] = value
-}
-
-func (v attrs) del(key string) {
- delete(v, key)
-}
-
-// Init returns request with new context and attribute bag.
-func Init(r *http.Request) *http.Request {
- // do not overwrite psr attributes
- if val := r.Context().Value(PsrContextKey); val == nil {
- return r.WithContext(context.WithValue(r.Context(), PsrContextKey, attrs{}))
- }
- return r
-}
-
-// All returns all context attributes.
-func All(r *http.Request) map[string]interface{} {
- v := r.Context().Value(PsrContextKey)
- if v == nil {
- return attrs{}
- }
-
- return v.(attrs)
-}
-
-// Get gets the value from request context. It replaces any existing
-// values.
-func Get(r *http.Request, key string) interface{} {
- v := r.Context().Value(PsrContextKey)
- if v == nil {
- return nil
- }
-
- return v.(attrs).get(key)
-}
-
-// Set sets the key to value. It replaces any existing
-// values. Context specific.
-func Set(r *http.Request, key string, value interface{}) error {
- v := r.Context().Value(PsrContextKey)
- if v == nil {
- return errors.New("unable to find `psr:attributes` context key")
- }
-
- v.(attrs).set(key, value)
- return nil
-}
-
-// Delete deletes values associated with attribute key.
-func (v attrs) Delete(key string) {
- if v == nil {
- return
- }
-
- v.del(key)
-}
diff --git a/plugins/http/config/fcgi.go b/plugins/http/config/fcgi.go
deleted file mode 100644
index 3d4acbe1..00000000
--- a/plugins/http/config/fcgi.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package config
-
-// FCGI for FastCGI server.
-type FCGI struct {
- // Address and port to handle as http server.
- Address string
-}
diff --git a/plugins/http/config/http.go b/plugins/http/config/http.go
deleted file mode 100644
index f06adc49..00000000
--- a/plugins/http/config/http.go
+++ /dev/null
@@ -1,187 +0,0 @@
-package config
-
-import (
- "net"
- "runtime"
- "strings"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/pool"
-)
-
-// HTTP configures RoadRunner HTTP server.
-type HTTP struct {
- // Host and port to handle as http server.
- Address string
-
- // InternalErrorCode used to override default 500 (InternalServerError) http code
- InternalErrorCode uint64 `mapstructure:"internal_error_code"`
-
- // SSLConfig defines https server options.
- SSLConfig *SSL `mapstructure:"ssl"`
-
- // FCGIConfig configuration. You can use FastCGI without HTTP server.
- FCGIConfig *FCGI `mapstructure:"fcgi"`
-
- // HTTP2Config configuration
- HTTP2Config *HTTP2 `mapstructure:"http2"`
-
- // MaxRequestSize specified max size for payload body in megabytes, set 0 to unlimited.
- MaxRequestSize uint64 `mapstructure:"max_request_size"`
-
- // TrustedSubnets declare IP subnets which are allowed to set ip using X-Real-Ip and X-Forwarded-For
- TrustedSubnets []string `mapstructure:"trusted_subnets"`
-
- // Uploads configures uploads configuration.
- Uploads *Uploads `mapstructure:"uploads"`
-
- // Pool configures worker pool.
- Pool *pool.Config `mapstructure:"pool"`
-
- // Env is environment variables passed to the http pool
- Env map[string]string
-
- // List of the middleware names (order will be preserved)
- Middleware []string
-
- // slice of net.IPNet
- Cidrs Cidrs
-}
-
-// EnableHTTP is true when http server must run.
-func (c *HTTP) EnableHTTP() bool {
- return c.Address != ""
-}
-
-// EnableTLS returns true if pool must listen TLS connections.
-func (c *HTTP) EnableTLS() bool {
- return c.SSLConfig.Key != "" || c.SSLConfig.Cert != ""
-}
-
-// EnableH2C when HTTP/2 extension must be enabled on TCP.
-func (c *HTTP) EnableH2C() bool {
- return c.HTTP2Config.H2C
-}
-
-// EnableFCGI is true when FastCGI server must be enabled.
-func (c *HTTP) EnableFCGI() bool {
- return c.FCGIConfig.Address != ""
-}
-
-// InitDefaults must populate HTTP values using given HTTP source. Must return error if HTTP is not valid.
-func (c *HTTP) InitDefaults() error {
- if c.Pool == nil {
- // default pool
- c.Pool = &pool.Config{
- Debug: false,
- NumWorkers: uint64(runtime.NumCPU()),
- MaxJobs: 0,
- AllocateTimeout: time.Second * 60,
- DestroyTimeout: time.Second * 60,
- Supervisor: nil,
- }
- }
-
- if c.InternalErrorCode == 0 {
- c.InternalErrorCode = 500
- }
-
- if c.HTTP2Config == nil {
- c.HTTP2Config = &HTTP2{}
- }
-
- if c.FCGIConfig == nil {
- c.FCGIConfig = &FCGI{}
- }
-
- if c.Uploads == nil {
- c.Uploads = &Uploads{}
- }
-
- if c.SSLConfig == nil {
- c.SSLConfig = &SSL{}
- }
-
- if c.SSLConfig.Address == "" {
- c.SSLConfig.Address = "127.0.0.1:443"
- }
-
- err := c.HTTP2Config.InitDefaults()
- if err != nil {
- return err
- }
- err = c.Uploads.InitDefaults()
- if err != nil {
- return err
- }
-
- if c.TrustedSubnets == nil {
- // @see https://en.wikipedia.org/wiki/Reserved_IP_addresses
- c.TrustedSubnets = []string{
- "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",
- }
- }
-
- cidrs, err := ParseCIDRs(c.TrustedSubnets)
- if err != nil {
- return err
- }
- c.Cidrs = cidrs
-
- return c.Valid()
-}
-
-// ParseCIDRs parse IPNet addresses and return slice of its
-func ParseCIDRs(subnets []string) (Cidrs, error) {
- c := make(Cidrs, 0, len(subnets))
- for _, cidr := range subnets {
- _, cr, err := net.ParseCIDR(cidr)
- if err != nil {
- return nil, err
- }
-
- c = append(c, cr)
- }
-
- return c, nil
-}
-
-// Valid validates the configuration.
-func (c *HTTP) Valid() error {
- const op = errors.Op("validation")
- if c.Uploads == nil {
- return errors.E(op, errors.Str("malformed uploads config"))
- }
-
- if c.HTTP2Config == nil {
- return errors.E(op, errors.Str("malformed http2 config"))
- }
-
- if c.Pool == nil {
- return errors.E(op, "malformed pool config")
- }
-
- if !c.EnableHTTP() && !c.EnableTLS() && !c.EnableFCGI() {
- return errors.E(op, errors.Str("unable to run http service, no method has been specified (http, https, http/2 or FastCGI)"))
- }
-
- if c.Address != "" && !strings.Contains(c.Address, ":") {
- return errors.E(op, errors.Str("malformed http server address"))
- }
-
- if c.EnableTLS() {
- err := c.SSLConfig.Valid()
- if err != nil {
- return errors.E(op, err)
- }
- }
-
- return nil
-}
diff --git a/plugins/http/config/http2.go b/plugins/http/config/http2.go
deleted file mode 100644
index b1e109e9..00000000
--- a/plugins/http/config/http2.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package config
-
-// HTTP2 HTTP/2 server customizations.
-type HTTP2 struct {
- // h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic
- // that should be h2c traffic. There are two ways to begin a h2c connection
- // (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this
- // works by starting an h2c connection with a string of bytes that is valid
- // HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to
- // h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to
- // h2c. When either of those situations occur we hijack the HTTP/1 connection,
- // convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn.
-
- // H2C enables HTTP/2 over TCP
- H2C bool
-
- // MaxConcurrentStreams defaults to 128.
- MaxConcurrentStreams uint32 `mapstructure:"max_concurrent_streams"`
-}
-
-// InitDefaults sets default values for HTTP/2 configuration.
-func (cfg *HTTP2) InitDefaults() error {
- if cfg.MaxConcurrentStreams == 0 {
- cfg.MaxConcurrentStreams = 128
- }
-
- return nil
-}
diff --git a/plugins/http/config/ip.go b/plugins/http/config/ip.go
deleted file mode 100644
index c4981f74..00000000
--- a/plugins/http/config/ip.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package config
-
-import "net"
-
-// Cidrs is a slice of IPNet addresses
-type Cidrs []*net.IPNet
-
-// IsTrusted checks if the ip address exists in the provided in the config addresses
-func (c *Cidrs) IsTrusted(ip string) bool {
- if len(*c) == 0 {
- return false
- }
-
- i := net.ParseIP(ip)
- if i == nil {
- return false
- }
-
- for _, cird := range *c {
- if cird.Contains(i) {
- return true
- }
- }
-
- return false
-}
diff --git a/plugins/http/config/ssl.go b/plugins/http/config/ssl.go
deleted file mode 100644
index 0e3c0caf..00000000
--- a/plugins/http/config/ssl.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package config
-
-import (
- "os"
- "strconv"
- "strings"
-
- "github.com/spiral/errors"
-)
-
-// SSL defines https server configuration.
-type SSL struct {
- // Address to listen as HTTPS server, defaults to 0.0.0.0:443.
- Address string
-
- // Redirect when enabled forces all http connections to switch to https.
- Redirect bool
-
- // Key defined private server key.
- Key string
-
- // Cert is https certificate.
- Cert string
-
- // Root CA file
- RootCA string `mapstructure:"root_ca"`
-
- // internal
- host string
- Port int
-}
-
-func (s *SSL) Valid() error {
- const op = errors.Op("ssl_valid")
-
- parts := strings.Split(s.Address, ":")
- switch len(parts) {
- // :443 form
- // 127.0.0.1:443 form
- // use 0.0.0.0 as host and 443 as port
- case 2:
- if parts[0] == "" {
- s.host = "127.0.0.1"
- } else {
- s.host = parts[0]
- }
-
- port, err := strconv.Atoi(parts[1])
- if err != nil {
- return errors.E(op, err)
- }
- s.Port = port
- default:
- return errors.E(op, errors.Errorf("unknown format, accepted format is [:<port> or <host>:<port>], provided: %s", s.Address))
- }
-
- if _, err := os.Stat(s.Key); err != nil {
- if os.IsNotExist(err) {
- return errors.E(op, errors.Errorf("key file '%s' does not exists", s.Key))
- }
-
- return err
- }
-
- if _, err := os.Stat(s.Cert); err != nil {
- if os.IsNotExist(err) {
- return errors.E(op, errors.Errorf("cert file '%s' does not exists", s.Cert))
- }
-
- return err
- }
-
- // RootCA is optional, but if provided - check it
- if s.RootCA != "" {
- if _, err := os.Stat(s.RootCA); err != nil {
- if os.IsNotExist(err) {
- return errors.E(op, errors.Errorf("root ca path provided, but path '%s' does not exists", s.RootCA))
- }
- return err
- }
- }
-
- return nil
-}
diff --git a/plugins/http/config/ssl_config_test.go b/plugins/http/config/ssl_config_test.go
deleted file mode 100644
index 8f6cf40e..00000000
--- a/plugins/http/config/ssl_config_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package config
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestSSL_Valid1(t *testing.T) {
- conf := &SSL{
- Address: "",
- Redirect: false,
- Key: "",
- Cert: "",
- RootCA: "",
- host: "",
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid2(t *testing.T) {
- conf := &SSL{
- Address: ":hello",
- Redirect: false,
- Key: "",
- Cert: "",
- RootCA: "",
- host: "",
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid3(t *testing.T) {
- conf := &SSL{
- Address: ":555",
- Redirect: false,
- Key: "",
- Cert: "",
- RootCA: "",
- host: "",
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid4(t *testing.T) {
- conf := &SSL{
- Address: ":555",
- Redirect: false,
- Key: "../../../tests/plugins/http/fixtures/server.key",
- Cert: "../../../tests/plugins/http/fixtures/server.crt",
- RootCA: "",
- host: "",
- // private
- Port: 0,
- }
-
- err := conf.Valid()
- assert.NoError(t, err)
-}
-
-func TestSSL_Valid5(t *testing.T) {
- conf := &SSL{
- Address: "a:b:c",
- Redirect: false,
- Key: "../../../tests/plugins/http/fixtures/server.key",
- Cert: "../../../tests/plugins/http/fixtures/server.crt",
- RootCA: "",
- host: "",
- // private
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid6(t *testing.T) {
- conf := &SSL{
- Address: ":",
- Redirect: false,
- Key: "../../../tests/plugins/http/fixtures/server.key",
- Cert: "../../../tests/plugins/http/fixtures/server.crt",
- RootCA: "",
- host: "",
- // private
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid7(t *testing.T) {
- conf := &SSL{
- Address: "127.0.0.1:555:1",
- Redirect: false,
- Key: "../../../tests/plugins/http/fixtures/server.key",
- Cert: "../../../tests/plugins/http/fixtures/server.crt",
- RootCA: "",
- host: "",
- // private
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
diff --git a/plugins/http/config/uploads_config.go b/plugins/http/config/uploads_config.go
deleted file mode 100644
index 5edb0ab7..00000000
--- a/plugins/http/config/uploads_config.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package config
-
-import (
- "os"
- "path"
- "strings"
-)
-
-// Uploads describes file location and controls access to them.
-type Uploads struct {
- // Dir contains name of directory to control access to.
- Dir string
-
- // Forbid specifies list of file extensions which are forbidden for access.
- // Example: .php, .exe, .bat, .htaccess and etc.
- Forbid []string
-}
-
-// InitDefaults sets missing values to their default values.
-func (cfg *Uploads) InitDefaults() error {
- cfg.Forbid = []string{".php", ".exe", ".bat"}
- cfg.Dir = os.TempDir()
- return nil
-}
-
-// TmpDir returns temporary directory.
-func (cfg *Uploads) TmpDir() string {
- if cfg.Dir != "" {
- return cfg.Dir
- }
-
- return os.TempDir()
-}
-
-// Forbids must return true if file extension is not allowed for the upload.
-func (cfg *Uploads) Forbids(filename string) bool {
- ext := strings.ToLower(path.Ext(filename))
-
- for _, v := range cfg.Forbid {
- if ext == v {
- return true
- }
- }
-
- return false
-}
diff --git a/plugins/http/metrics.go b/plugins/http/metrics.go
deleted file mode 100644
index d7a9110b..00000000
--- a/plugins/http/metrics.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package http
-
-import (
- "strconv"
-
- "github.com/prometheus/client_golang/prometheus"
- handler "github.com/spiral/roadrunner/v2/pkg/worker_handler"
-)
-
-func (p *Plugin) MetricsCollector() []prometheus.Collector {
- // p - implements Exporter interface (workers)
- // other - request duration and count
- return []prometheus.Collector{p, p.requestsExporter.requestDuration, p.requestsExporter.requestCounter}
-}
-
-func (p *Plugin) metricsCallback(event interface{}) {
- switch e := event.(type) {
- case handler.ResponseEvent:
- p.requestsExporter.requestCounter.With(prometheus.Labels{
- "status": strconv.Itoa(e.Response.Status),
- }).Inc()
-
- p.requestsExporter.requestDuration.With(prometheus.Labels{
- "status": strconv.Itoa(e.Response.Status),
- }).Observe(e.Elapsed().Seconds())
- case handler.ErrorEvent:
- p.requestsExporter.requestCounter.With(prometheus.Labels{
- "status": "500",
- }).Inc()
-
- p.requestsExporter.requestDuration.With(prometheus.Labels{
- "status": "500",
- }).Observe(e.Elapsed().Seconds())
- }
-}
-
-type workersExporter struct {
- wm *prometheus.Desc
- workersMemory uint64
-}
-
-func newWorkersExporter() *workersExporter {
- return &workersExporter{
- wm: prometheus.NewDesc("rr_http_workers_memory_bytes", "Memory usage by HTTP workers.", nil, nil),
- workersMemory: 0,
- }
-}
-
-func (p *Plugin) Describe(d chan<- *prometheus.Desc) {
- // send description
- d <- p.workersExporter.wm
-}
-
-func (p *Plugin) Collect(ch chan<- prometheus.Metric) {
- // get the copy of the processes
- workers := p.Workers()
-
- // cumulative RSS memory in bytes
- var cum uint64
-
- // collect the memory
- for i := 0; i < len(workers); i++ {
- cum += workers[i].MemoryUsage
- }
-
- // send the values to the prometheus
- ch <- prometheus.MustNewConstMetric(p.workersExporter.wm, prometheus.GaugeValue, float64(cum))
-}
-
-type requestsExporter struct {
- requestCounter *prometheus.CounterVec
- requestDuration *prometheus.HistogramVec
-}
-
-func newRequestsExporter() *requestsExporter {
- return &requestsExporter{
- requestCounter: prometheus.NewCounterVec(
- prometheus.CounterOpts{
- Name: "rr_http_request_total",
- Help: "Total number of handled http requests after server restart.",
- },
- []string{"status"},
- ),
- requestDuration: prometheus.NewHistogramVec(
- prometheus.HistogramOpts{
- Name: "rr_http_request_duration_seconds",
- Help: "HTTP request duration.",
- },
- []string{"status"},
- ),
- }
-}
diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go
deleted file mode 100644
index dc887f87..00000000
--- a/plugins/http/plugin.go
+++ /dev/null
@@ -1,412 +0,0 @@
-package http
-
-import (
- "context"
- "fmt"
- "log"
- "net/http"
- "sync"
-
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/pool"
- "github.com/spiral/roadrunner/v2/pkg/state/process"
- "github.com/spiral/roadrunner/v2/pkg/worker"
- handler "github.com/spiral/roadrunner/v2/pkg/worker_handler"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/http/attributes"
- httpConfig "github.com/spiral/roadrunner/v2/plugins/http/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/server"
- "github.com/spiral/roadrunner/v2/plugins/status"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
-)
-
-const (
- // PluginName declares plugin name.
- PluginName = "http"
-
- // RrMode RR_HTTP env variable key (internal) if the HTTP presents
- RrMode = "RR_MODE"
-
- HTTPSScheme = "https"
-)
-
-// Middleware interface
-type Middleware interface {
- Middleware(f http.Handler) http.Handler
-}
-
-type middleware map[string]Middleware
-
-// Plugin manages pool, http servers. The main http plugin structure
-type Plugin struct {
- sync.RWMutex
-
- // plugins
- server server.Server
- log logger.Logger
- // stdlog passed to the http/https/fcgi servers to log their internal messages
- stdLog *log.Logger
-
- // http configuration
- cfg *httpConfig.HTTP `mapstructure:"http"`
-
- // middlewares to chain
- mdwr middleware
-
- // Pool which attached to all servers
- pool pool.Pool
-
- // servers RR handler
- handler *handler.Handler
-
- // metrics
- workersExporter *workersExporter
- requestsExporter *requestsExporter
-
- // servers
- http *http.Server
- https *http.Server
- fcgi *http.Server
-}
-
-// Init must return configure svc and return true if svc hasStatus enabled. Must return error in case of
-// misconfiguration. Services must not be used without proper configuration pushed first.
-func (p *Plugin) Init(cfg config.Configurer, rrLogger logger.Logger, server server.Server) error {
- const op = errors.Op("http_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(PluginName, &p.cfg)
- if err != nil {
- return errors.E(op, err)
- }
-
- err = p.cfg.InitDefaults()
- if err != nil {
- return errors.E(op, err)
- }
-
- // rr logger (via plugin)
- p.log = rrLogger
- // use time and date in UTC format
- p.stdLog = log.New(logger.NewStdAdapter(p.log), "http_plugin: ", log.Ldate|log.Ltime|log.LUTC)
-
- p.mdwr = make(map[string]Middleware)
-
- if !p.cfg.EnableHTTP() && !p.cfg.EnableTLS() && !p.cfg.EnableFCGI() {
- return errors.E(op, errors.Disabled)
- }
-
- // init if nil
- if p.cfg.Env == nil {
- p.cfg.Env = make(map[string]string)
- }
-
- // initialize workersExporter
- p.workersExporter = newWorkersExporter()
- // initialize requests exporter
- p.requestsExporter = newRequestsExporter()
-
- p.cfg.Env[RrMode] = "http"
- p.server = server
-
- return nil
-}
-
-func (p *Plugin) logCallback(event interface{}) {
- if ev, ok := event.(handler.ResponseEvent); ok {
- p.log.Debug(fmt.Sprintf("%d %s %s", ev.Response.Status, ev.Request.Method, ev.Request.URI),
- "remote", ev.Request.RemoteAddr,
- "elapsed", ev.Elapsed().String(),
- )
- }
-}
-
-// Serve serves the svc.
-func (p *Plugin) Serve() chan error {
- errCh := make(chan error, 2)
- // run whole process in the goroutine
- go func() {
- // protect http initialization
- p.Lock()
- p.serve(errCh)
- p.Unlock()
- }()
-
- return errCh
-}
-
-func (p *Plugin) serve(errCh chan error) {
- var err error
- const op = errors.Op("http_plugin_serve")
- p.pool, err = p.server.NewWorkerPool(context.Background(), &pool.Config{
- Debug: p.cfg.Pool.Debug,
- NumWorkers: p.cfg.Pool.NumWorkers,
- MaxJobs: p.cfg.Pool.MaxJobs,
- AllocateTimeout: p.cfg.Pool.AllocateTimeout,
- DestroyTimeout: p.cfg.Pool.DestroyTimeout,
- Supervisor: p.cfg.Pool.Supervisor,
- }, p.cfg.Env, p.logCallback)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- p.handler, err = handler.NewHandler(
- p.cfg.MaxRequestSize,
- p.cfg.InternalErrorCode,
- *p.cfg.Uploads,
- p.cfg.Cidrs,
- p.pool,
- )
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- p.handler.AddListener(p.logCallback, p.metricsCallback)
-
- if p.cfg.EnableHTTP() {
- if p.cfg.EnableH2C() {
- p.http = &http.Server{
- Handler: h2c.NewHandler(p, &http2.Server{}),
- ErrorLog: p.stdLog,
- }
- } else {
- p.http = &http.Server{
- Handler: p,
- ErrorLog: p.stdLog,
- }
- }
- }
-
- if p.cfg.EnableTLS() {
- p.https = p.initSSL()
- if p.cfg.SSLConfig.RootCA != "" {
- err = p.appendRootCa()
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
- }
-
- // if HTTP2Config not nil
- if p.cfg.HTTP2Config != nil {
- if err := p.initHTTP2(); err != nil {
- errCh <- errors.E(op, err)
- return
- }
- }
- }
-
- if p.cfg.EnableFCGI() {
- p.fcgi = &http.Server{Handler: p, ErrorLog: p.stdLog}
- }
-
- // start http, https and fcgi servers if requested in the config
- go func() {
- p.serveHTTP(errCh)
- }()
-
- go func() {
- p.serveHTTPS(errCh)
- }()
-
- go func() {
- p.serveFCGI(errCh)
- }()
-}
-
-// Stop stops the http.
-func (p *Plugin) Stop() error {
- p.Lock()
- defer p.Unlock()
-
- if p.fcgi != nil {
- err := p.fcgi.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- p.log.Error("fcgi shutdown", "error", err)
- }
- }
-
- if p.https != nil {
- err := p.https.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- p.log.Error("https shutdown", "error", err)
- }
- }
-
- if p.http != nil {
- err := p.http.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- p.log.Error("http shutdown", "error", err)
- }
- }
-
- // check for safety
- if p.pool != nil {
- p.pool.Destroy(context.Background())
- }
-
- return nil
-}
-
-// ServeHTTP handles connection using set of middleware and pool PSR-7 server.
-func (p *Plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- defer func() {
- err := r.Body.Close()
- if err != nil {
- p.log.Error("body close", "error", err)
- }
- }()
- if headerContainsUpgrade(r) {
- http.Error(w, "server does not support upgrade header", http.StatusInternalServerError)
- return
- }
-
- if p.https != nil && r.TLS == nil && p.cfg.SSLConfig.Redirect {
- p.redirect(w, r)
- return
- }
-
- if p.https != nil && r.TLS != nil {
- w.Header().Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
- }
-
- r = attributes.Init(r)
- // protect the case, when user sendEvent Reset and we are replacing handler with pool
- p.RLock()
- p.handler.ServeHTTP(w, r)
- p.RUnlock()
-}
-
-// Workers returns slice with the process states for the workers
-func (p *Plugin) Workers() []*process.State {
- p.RLock()
- defer p.RUnlock()
-
- workers := p.workers()
-
- ps := make([]*process.State, 0, len(workers))
- for i := 0; i < len(workers); i++ {
- state, err := process.WorkerProcessState(workers[i])
- if err != nil {
- return nil
- }
- ps = append(ps, state)
- }
-
- return ps
-}
-
-// internal
-func (p *Plugin) workers() []worker.BaseProcess {
- return p.pool.Workers()
-}
-
-// Name returns endure.Named interface implementation
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-// Reset destroys the old pool and replaces it with new one, waiting for old pool to die
-func (p *Plugin) Reset() error {
- p.Lock()
- defer p.Unlock()
- const op = errors.Op("http_plugin_reset")
- p.log.Info("HTTP plugin got restart request. Restarting...")
- p.pool.Destroy(context.Background())
- p.pool = nil
-
- var err error
- p.pool, err = p.server.NewWorkerPool(context.Background(), &pool.Config{
- Debug: p.cfg.Pool.Debug,
- NumWorkers: p.cfg.Pool.NumWorkers,
- MaxJobs: p.cfg.Pool.MaxJobs,
- AllocateTimeout: p.cfg.Pool.AllocateTimeout,
- DestroyTimeout: p.cfg.Pool.DestroyTimeout,
- Supervisor: p.cfg.Pool.Supervisor,
- }, p.cfg.Env, p.logCallback)
- if err != nil {
- return errors.E(op, err)
- }
-
- p.log.Info("HTTP workers Pool successfully restarted")
-
- p.handler, err = handler.NewHandler(
- p.cfg.MaxRequestSize,
- p.cfg.InternalErrorCode,
- *p.cfg.Uploads,
- p.cfg.Cidrs,
- p.pool,
- )
-
- if err != nil {
- return errors.E(op, err)
- }
-
- p.log.Info("HTTP handler listeners successfully re-added")
- p.handler.AddListener(p.logCallback, p.metricsCallback)
-
- p.log.Info("HTTP plugin successfully restarted")
- return nil
-}
-
-// Collects collecting http middlewares
-func (p *Plugin) Collects() []interface{} {
- return []interface{}{
- p.AddMiddleware,
- }
-}
-
-// AddMiddleware is base requirement for the middleware (name and Middleware)
-func (p *Plugin) AddMiddleware(name endure.Named, m Middleware) {
- p.mdwr[name.Name()] = m
-}
-
-// Status return status of the particular plugin
-func (p *Plugin) Status() status.Status {
- p.RLock()
- defer p.RUnlock()
-
- workers := p.workers()
- for i := 0; i < len(workers); i++ {
- if workers[i].State().IsActive() {
- return status.Status{
- Code: http.StatusOK,
- }
- }
- }
- // if there are no workers, threat this as error
- return status.Status{
- Code: http.StatusServiceUnavailable,
- }
-}
-
-// Ready return readiness status of the particular plugin
-func (p *Plugin) Ready() status.Status {
- p.RLock()
- defer p.RUnlock()
-
- workers := p.workers()
- for i := 0; i < len(workers); i++ {
- // If state of the worker is ready (at least 1)
- // we assume, that plugin's worker pool is ready
- if workers[i].State().Value() == worker.StateReady {
- return status.Status{
- Code: http.StatusOK,
- }
- }
- }
- // if there are no workers, threat this as no content error
- return status.Status{
- Code: http.StatusServiceUnavailable,
- }
-}
-
-// Available interface implementation
-func (p *Plugin) Available() {}
diff --git a/plugins/http/serve.go b/plugins/http/serve.go
deleted file mode 100644
index 6d3f2228..00000000
--- a/plugins/http/serve.go
+++ /dev/null
@@ -1,254 +0,0 @@
-package http
-
-import (
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "net/http"
- "net/http/fcgi"
- "net/url"
- "os"
- "strings"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/utils"
- "golang.org/x/net/http2"
- "golang.org/x/sys/cpu"
-)
-
-func (p *Plugin) serveHTTP(errCh chan error) {
- if p.http == nil {
- return
- }
- const op = errors.Op("serveHTTP")
-
- if len(p.mdwr) > 0 {
- applyMiddlewares(p.http, p.mdwr, p.cfg.Middleware, p.log)
- }
- l, err := utils.CreateListener(p.cfg.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = p.http.Serve(l)
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
-func (p *Plugin) serveHTTPS(errCh chan error) {
- if p.https == nil {
- return
- }
- const op = errors.Op("serveHTTPS")
- if len(p.mdwr) > 0 {
- applyMiddlewares(p.https, p.mdwr, p.cfg.Middleware, p.log)
- }
- l, err := utils.CreateListener(p.cfg.SSLConfig.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = p.https.ServeTLS(
- l,
- p.cfg.SSLConfig.Cert,
- p.cfg.SSLConfig.Key,
- )
-
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
-// serveFCGI starts FastCGI server.
-func (p *Plugin) serveFCGI(errCh chan error) {
- if p.fcgi == nil {
- return
- }
- const op = errors.Op("serveFCGI")
-
- if len(p.mdwr) > 0 {
- applyMiddlewares(p.fcgi, p.mdwr, p.cfg.Middleware, p.log)
- }
-
- l, err := utils.CreateListener(p.cfg.FCGIConfig.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = fcgi.Serve(l, p.fcgi.Handler)
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
-func (p *Plugin) redirect(w http.ResponseWriter, r *http.Request) {
- target := &url.URL{
- Scheme: HTTPSScheme,
- // host or host:port
- Host: p.tlsAddr(r.Host, false),
- Path: r.URL.Path,
- RawQuery: r.URL.RawQuery,
- }
-
- http.Redirect(w, r, target.String(), http.StatusPermanentRedirect)
-}
-
-// https://golang.org/pkg/net/http/#Hijacker
-//go:inline
-func headerContainsUpgrade(r *http.Request) bool {
- if _, ok := r.Header["Upgrade"]; ok {
- return true
- }
- return false
-}
-
-// append RootCA to the https server TLS config
-func (p *Plugin) appendRootCa() error {
- const op = errors.Op("http_plugin_append_root_ca")
- rootCAs, err := x509.SystemCertPool()
- if err != nil {
- return nil
- }
- if rootCAs == nil {
- rootCAs = x509.NewCertPool()
- }
-
- CA, err := os.ReadFile(p.cfg.SSLConfig.RootCA)
- if err != nil {
- return err
- }
-
- // should append our CA cert
- ok := rootCAs.AppendCertsFromPEM(CA)
- if !ok {
- return errors.E(op, errors.Str("could not append Certs from PEM"))
- }
- // disable "G402 (CWE-295): TLS MinVersion too low. (Confidence: HIGH, Severity: HIGH)"
- // #nosec G402
- cfg := &tls.Config{
- InsecureSkipVerify: false,
- RootCAs: rootCAs,
- }
- p.http.TLSConfig = cfg
-
- return nil
-}
-
-// Init https server
-func (p *Plugin) initSSL() *http.Server {
- var topCipherSuites []uint16
- var defaultCipherSuitesTLS13 []uint16
-
- hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
- hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
- // Keep in sync with crypto/aes/cipher_s390x.go.
- hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
-
- hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
-
- if hasGCMAsm {
- // If AES-GCM hardware is provided then priorities AES-GCM
- // cipher suites.
- topCipherSuites = []uint16{
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- }
- defaultCipherSuitesTLS13 = []uint16{
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_AES_256_GCM_SHA384,
- }
- } else {
- // Without AES-GCM hardware, we put the ChaCha20-Poly1305
- // cipher suites first.
- topCipherSuites = []uint16{
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- }
- defaultCipherSuitesTLS13 = []uint16{
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_AES_256_GCM_SHA384,
- }
- }
-
- DefaultCipherSuites := make([]uint16, 0, 22)
- DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...)
- DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...)
-
- sslServer := &http.Server{
- Addr: p.tlsAddr(p.cfg.Address, true),
- Handler: p,
- ErrorLog: p.stdLog,
- TLSConfig: &tls.Config{
- CurvePreferences: []tls.CurveID{
- tls.CurveP256,
- tls.CurveP384,
- tls.CurveP521,
- tls.X25519,
- },
- CipherSuites: DefaultCipherSuites,
- MinVersion: tls.VersionTLS12,
- PreferServerCipherSuites: true,
- },
- }
-
- return sslServer
-}
-
-// init http/2 server
-func (p *Plugin) initHTTP2() error {
- return http2.ConfigureServer(p.https, &http2.Server{
- MaxConcurrentStreams: p.cfg.HTTP2Config.MaxConcurrentStreams,
- })
-}
-
-// tlsAddr replaces listen or host port with port configured by SSLConfig config.
-func (p *Plugin) tlsAddr(host string, forcePort bool) string {
- // remove current forcePort first
- host = strings.Split(host, ":")[0]
-
- if forcePort || p.cfg.SSLConfig.Port != 443 {
- host = fmt.Sprintf("%s:%v", host, p.cfg.SSLConfig.Port)
- }
-
- return host
-}
-
-// static plugin name
-const static string = "static"
-
-func applyMiddlewares(server *http.Server, middlewares map[string]Middleware, order []string, log logger.Logger) {
- for i := len(order) - 1; i >= 0; i-- {
- // set static last in the row
- if order[i] == static {
- continue
- }
- if mdwr, ok := middlewares[order[i]]; ok {
- server.Handler = mdwr.Middleware(server.Handler)
- } else {
- log.Warn("requested middleware does not exist", "requested", order[i])
- }
- }
-
- // set static if exists
- if mdwr, ok := middlewares[static]; ok {
- server.Handler = mdwr.Middleware(server.Handler)
- }
-}
diff --git a/plugins/informer/interface.go b/plugins/informer/interface.go
deleted file mode 100644
index 9277b85b..00000000
--- a/plugins/informer/interface.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package informer
-
-import (
- "context"
-
- "github.com/spiral/roadrunner/v2/pkg/state/job"
- "github.com/spiral/roadrunner/v2/pkg/state/process"
-)
-
-/*
-Informer plugin should not receive any other plugin in the Init or via Collects
-Because Availabler implementation should present in every plugin
-*/
-
-// Statistic interfaces ==============
-
-// Informer used to get workers from particular plugin or set of plugins
-type Informer interface {
- Workers() []*process.State
-}
-
-// JobsStat interface provide statistic for the jobs plugin
-type JobsStat interface {
- // JobsState returns slice with the attached drivers information
- JobsState(ctx context.Context) ([]*job.State, error)
-}
-
-// Statistic interfaces end ============
-
-// Availabler interface should be implemented by every plugin which wish to report to the PHP worker that it available in the RR runtime
-type Availabler interface {
- // Available method needed to collect all plugins which are available in the runtime.
- Available()
-}
diff --git a/plugins/informer/plugin.go b/plugins/informer/plugin.go
deleted file mode 100644
index 87180be5..00000000
--- a/plugins/informer/plugin.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package informer
-
-import (
- "context"
-
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/roadrunner/v2/pkg/state/job"
- "github.com/spiral/roadrunner/v2/pkg/state/process"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const PluginName = "informer"
-
-type Plugin struct {
- log logger.Logger
-
- withJobs map[string]JobsStat
- withWorkers map[string]Informer
- available map[string]Availabler
-}
-
-func (p *Plugin) Init(log logger.Logger) error {
- p.available = make(map[string]Availabler)
- p.withWorkers = make(map[string]Informer)
- p.withJobs = make(map[string]JobsStat)
-
- p.log = log
- return nil
-}
-
-// Workers provides BaseProcess slice with workers for the requested plugin
-func (p *Plugin) Workers(name string) []*process.State {
- svc, ok := p.withWorkers[name]
- if !ok {
- return nil
- }
-
- return svc.Workers()
-}
-
-// Jobs provides information about jobs for the registered plugin using jobs
-func (p *Plugin) Jobs(name string) []*job.State {
- svc, ok := p.withJobs[name]
- if !ok {
- return nil
- }
-
- st, err := svc.JobsState(context.Background())
- if err != nil {
- p.log.Info("jobs stat", "error", err)
- // skip errors here
- return nil
- }
-
- return st
-}
-
-// Collects declares services to be collected.
-func (p *Plugin) Collects() []interface{} {
- return []interface{}{
- p.CollectPlugins,
- p.CollectWorkers,
- p.CollectJobs,
- }
-}
-
-// CollectPlugins collects all RR plugins
-func (p *Plugin) CollectPlugins(name endure.Named, l Availabler) {
- p.available[name.Name()] = l
-}
-
-// CollectWorkers obtains plugins with workers inside.
-func (p *Plugin) CollectWorkers(name endure.Named, r Informer) {
- p.withWorkers[name.Name()] = r
-}
-
-func (p *Plugin) CollectJobs(name endure.Named, j JobsStat) {
- p.withJobs[name.Name()] = j
-}
-
-// Name of the service.
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-// RPC returns associated rpc service.
-func (p *Plugin) RPC() interface{} {
- return &rpc{srv: p}
-}
diff --git a/plugins/informer/rpc.go b/plugins/informer/rpc.go
deleted file mode 100644
index 478d3227..00000000
--- a/plugins/informer/rpc.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package informer
-
-import (
- "github.com/spiral/roadrunner/v2/pkg/state/job"
- "github.com/spiral/roadrunner/v2/pkg/state/process"
-)
-
-type rpc struct {
- srv *Plugin
-}
-
-// WorkerList contains list of workers.
-type WorkerList struct {
- // Workers are list of workers.
- Workers []*process.State `json:"workers"`
-}
-
-// List all resettable services.
-func (rpc *rpc) List(_ bool, list *[]string) error {
- *list = make([]string, 0, len(rpc.srv.withWorkers))
-
- // append all plugin names to the output result
- for name := range rpc.srv.available {
- *list = append(*list, name)
- }
- return nil
-}
-
-// Workers state of a given service.
-func (rpc *rpc) Workers(service string, list *WorkerList) error {
- workers := rpc.srv.Workers(service)
- if workers == nil {
- return nil
- }
-
- // write actual processes
- list.Workers = workers
-
- return nil
-}
-
-func (rpc *rpc) Jobs(service string, out *[]*job.State) error {
- *out = rpc.srv.Jobs(service)
- return nil
-}
-
-// sort.Sort
-
-func (w *WorkerList) Len() int {
- return len(w.Workers)
-}
-
-func (w *WorkerList) Less(i, j int) bool {
- return w.Workers[i].Pid < w.Workers[j].Pid
-}
-
-func (w *WorkerList) Swap(i, j int) {
- w.Workers[i], w.Workers[j] = w.Workers[j], w.Workers[i]
-}
diff --git a/plugins/jobs/config.go b/plugins/jobs/config.go
deleted file mode 100644
index 454256b9..00000000
--- a/plugins/jobs/config.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package jobs
-
-import (
- "runtime"
-
- poolImpl "github.com/spiral/roadrunner/v2/pkg/pool"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
-)
-
-const (
- // name used to set pipeline name
- pipelineName string = "name"
-)
-
-// Config defines settings for job broker, workers and job-pipeline mapping.
-type Config struct {
- // NumPollers configures number of priority queue pollers
- // Should be no more than 255
- // Default - num logical cores
- NumPollers uint8 `mapstructure:"num_pollers"`
-
- // PipelineSize is the limit of a main jobs queue which consume Items from the drivers pipeline
- // Driver pipeline might be much larger than a main jobs queue
- PipelineSize uint64 `mapstructure:"pipeline_size"`
-
- // Timeout in seconds is the per-push limit to put the job into queue
- Timeout int `mapstructure:"timeout"`
-
- // Pool configures roadrunner workers pool.
- Pool *poolImpl.Config `mapstructure:"Pool"`
-
- // Pipelines defines mapping between PHP job pipeline and associated job broker.
- Pipelines map[string]*pipeline.Pipeline `mapstructure:"pipelines"`
-
- // Consuming specifies names of pipelines to be consumed on service start.
- Consume []string `mapstructure:"consume"`
-}
-
-func (c *Config) InitDefaults() {
- if c.Pool == nil {
- c.Pool = &poolImpl.Config{}
- }
-
- if c.PipelineSize == 0 {
- c.PipelineSize = 1_000_000
- }
-
- if c.NumPollers == 0 {
- c.NumPollers = uint8(runtime.NumCPU())
- }
-
- for k := range c.Pipelines {
- // set the pipeline name
- c.Pipelines[k].With(pipelineName, k)
- }
-
- if c.Timeout == 0 {
- c.Timeout = 60
- }
-
- c.Pool.InitDefaults()
-}
diff --git a/plugins/jobs/doc/jobs_arch.drawio b/plugins/jobs/doc/jobs_arch.drawio
deleted file mode 100644
index 824e1a83..00000000
--- a/plugins/jobs/doc/jobs_arch.drawio
+++ /dev/null
@@ -1 +0,0 @@
-<mxfile host="Electron" modified="2021-08-21T12:35:37.051Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/91.0.4472.164 Electron/13.2.1 Safari/537.36" etag="L_bYn0v_jW4MOvWLd2St" version="14.6.13" type="device"><diagram id="AFQlLRRq6yGg9IpTzkrs" name="Page-1">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</diagram></mxfile> \ No newline at end of file
diff --git a/plugins/jobs/doc/response_protocol.md b/plugins/jobs/doc/response_protocol.md
deleted file mode 100644
index e195c407..00000000
--- a/plugins/jobs/doc/response_protocol.md
+++ /dev/null
@@ -1,54 +0,0 @@
-Response protocol used to communicate between worker and RR. When a worker completes its job, it should send a typed
-response. The response should contain:
-
-1. `type` field with the message type. Can be treated as enums.
-2. `data` field with the dynamic response related to the type.
-
-Types are:
-
-```
-0 - NO_ERROR
-1 - ERROR
-2 - ...
-```
-
-- `NO_ERROR`: contains only `type` and empty `data`.
-- `ERROR` : contains `type`: 1, and `data` field with: `message` describing the error, `requeue` flag to requeue the
- job,
- `delay_seconds`: to delay a queue for a provided amount of seconds, `headers` - job's headers represented as hashmap
- with string key and array of strings as a value.
-
-For example:
-
-`NO_ERROR`:
-For example:
-
-```json
-{
- "type": 0,
- "data": {}
-}
-
-```
-
-`ERROR`:
-
-```json
-{
- "type": 1,
- "data": {
- "message": "internal worker error",
- "requeue": true,
- "headers": [
- {
- "test": [
- "1",
- "2",
- "3"
- ]
- }
- ],
- "delay_seconds": 10
- }
-}
-```
diff --git a/plugins/jobs/job/job.go b/plugins/jobs/job/job.go
deleted file mode 100644
index adab2a0a..00000000
--- a/plugins/jobs/job/job.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package job
-
-import (
- "time"
-)
-
-// constant keys to pack/unpack messages from different drivers
-const (
- RRID string = "rr_id"
- RRJob string = "rr_job"
- RRHeaders string = "rr_headers"
- RRPipeline string = "rr_pipeline"
- RRDelay string = "rr_delay"
- RRPriority string = "rr_priority"
-)
-
-// Job carries information about single job.
-type Job struct {
- // Job contains name of job broker (usually PHP class).
- Job string `json:"job"`
-
- // Ident is unique identifier of the job, should be provided from outside
- Ident string `json:"id"`
-
- // Payload is string data (usually JSON) passed to Job broker.
- Payload string `json:"payload"`
-
- // Headers with key-value pairs
- Headers map[string][]string `json:"headers"`
-
- // Options contains set of PipelineOptions specific to job execution. Can be empty.
- Options *Options `json:"options,omitempty"`
-}
-
-// Options carry information about how to handle given job.
-type Options struct {
- // Priority is job priority, default - 10
- // pointer to distinguish 0 as a priority and nil as priority not set
- Priority int64 `json:"priority"`
-
- // Pipeline manually specified pipeline.
- Pipeline string `json:"pipeline,omitempty"`
-
- // Delay defines time duration to delay execution for. Defaults to none.
- Delay int64 `json:"delay,omitempty"`
-}
-
-// DelayDuration returns delay duration in a form of time.Duration.
-func (o *Options) DelayDuration() time.Duration {
- return time.Second * time.Duration(o.Delay)
-}
diff --git a/plugins/jobs/job/job_test.go b/plugins/jobs/job/job_test.go
deleted file mode 100644
index 4a95e27d..00000000
--- a/plugins/jobs/job/job_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package job
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestOptions_DelayDuration(t *testing.T) {
- opts := &Options{Delay: 0}
- assert.Equal(t, time.Duration(0), opts.DelayDuration())
-}
-
-func TestOptions_DelayDuration2(t *testing.T) {
- opts := &Options{Delay: 1}
- assert.Equal(t, time.Second, opts.DelayDuration())
-}
diff --git a/plugins/jobs/metrics.go b/plugins/jobs/metrics.go
deleted file mode 100644
index 38d0bcfb..00000000
--- a/plugins/jobs/metrics.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package jobs
-
-import (
- "sync/atomic"
-
- "github.com/prometheus/client_golang/prometheus"
- "github.com/spiral/roadrunner/v2/pkg/events"
- "github.com/spiral/roadrunner/v2/plugins/informer"
-)
-
-func (p *Plugin) MetricsCollector() []prometheus.Collector {
- // p - implements Exporter interface (workers)
- // other - request duration and count
- return []prometheus.Collector{p.statsExporter}
-}
-
-const (
- namespace = "rr_jobs"
-)
-
-type statsExporter struct {
- workers informer.Informer
- workersMemory uint64
- jobsOk uint64
- pushOk uint64
- jobsErr uint64
- pushErr uint64
-}
-
-var (
- worker = prometheus.NewDesc("workers_memory_bytes", "Memory usage by JOBS workers.", nil, nil)
- pushOk = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "push_ok"), "Number of job push.", nil, nil)
- pushErr = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "push_err"), "Number of jobs push which was failed.", nil, nil)
- jobsErr = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "jobs_err"), "Number of jobs error while processing in the worker.", nil, nil)
- jobsOk = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "jobs_ok"), "Number of successfully processed jobs.", nil, nil)
-)
-
-func newStatsExporter(stats informer.Informer) *statsExporter {
- return &statsExporter{
- workers: stats,
- workersMemory: 0,
- jobsOk: 0,
- pushOk: 0,
- jobsErr: 0,
- pushErr: 0,
- }
-}
-
-func (se *statsExporter) metricsCallback(event interface{}) {
- if jev, ok := event.(events.JobEvent); ok {
- switch jev.Event { //nolint:exhaustive
- case events.EventJobOK:
- atomic.AddUint64(&se.jobsOk, 1)
- case events.EventPushOK:
- atomic.AddUint64(&se.pushOk, 1)
- case events.EventPushError:
- atomic.AddUint64(&se.pushErr, 1)
- case events.EventJobError:
- atomic.AddUint64(&se.jobsErr, 1)
- }
- }
-}
-
-func (se *statsExporter) Describe(d chan<- *prometheus.Desc) {
- // send description
- d <- worker
- d <- pushErr
- d <- pushOk
- d <- jobsErr
- d <- jobsOk
-}
-
-func (se *statsExporter) Collect(ch chan<- prometheus.Metric) {
- // get the copy of the processes
- workers := se.workers.Workers()
-
- // cumulative RSS memory in bytes
- var cum uint64
-
- // collect the memory
- for i := 0; i < len(workers); i++ {
- cum += workers[i].MemoryUsage
- }
-
- // send the values to the prometheus
- ch <- prometheus.MustNewConstMetric(worker, prometheus.GaugeValue, float64(cum))
- // send the values to the prometheus
- ch <- prometheus.MustNewConstMetric(jobsOk, prometheus.GaugeValue, float64(atomic.LoadUint64(&se.jobsOk)))
- ch <- prometheus.MustNewConstMetric(jobsErr, prometheus.GaugeValue, float64(atomic.LoadUint64(&se.jobsErr)))
- ch <- prometheus.MustNewConstMetric(pushOk, prometheus.GaugeValue, float64(atomic.LoadUint64(&se.pushOk)))
- ch <- prometheus.MustNewConstMetric(pushErr, prometheus.GaugeValue, float64(atomic.LoadUint64(&se.pushErr)))
-}
diff --git a/plugins/jobs/pipeline/pipeline.go b/plugins/jobs/pipeline/pipeline.go
deleted file mode 100644
index 8a8c1462..00000000
--- a/plugins/jobs/pipeline/pipeline.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package pipeline
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-// Pipeline defines pipeline options.
-type Pipeline map[string]interface{}
-
-const (
- priority string = "priority"
- driver string = "driver"
- name string = "name"
-)
-
-// With pipeline value
-func (p *Pipeline) With(name string, value interface{}) {
- (*p)[name] = value
-}
-
-// Name returns pipeline name.
-func (p Pipeline) Name() string {
- return p.String(name, "")
-}
-
-// Driver associated with the pipeline.
-func (p Pipeline) Driver() string {
- return p.String(driver, "")
-}
-
-// Has checks if value presented in pipeline.
-func (p Pipeline) Has(name string) bool {
- if _, ok := p[name]; ok {
- return true
- }
-
- return false
-}
-
-// String must return option value as string or return default value.
-func (p Pipeline) String(name string, d string) string {
- if value, ok := p[name]; ok {
- if str, ok := value.(string); ok {
- return str
- }
- }
-
- return d
-}
-
-// Int must return option value as string or return default value.
-func (p Pipeline) Int(name string, d int) int {
- if value, ok := p[name]; ok {
- if i, ok := value.(int); ok {
- return i
- }
- }
-
- return d
-}
-
-// Bool must return option value as bool or return default value.
-func (p Pipeline) Bool(name string, d bool) bool {
- if value, ok := p[name]; ok {
- if i, ok := value.(bool); ok {
- return i
- }
- }
-
- return d
-}
-
-// Map must return nested map value or empty config.
-// Here might be sqs attributes or tags for example
-func (p Pipeline) Map(name string, out map[string]string) error {
- if value, ok := p[name]; ok {
- if m, ok := value.(string); ok {
- err := json.Unmarshal(utils.AsBytes(m), &out)
- if err != nil {
- return err
- }
- }
- }
-
- return nil
-}
-
-// Priority returns default pipeline priority
-func (p Pipeline) Priority() int64 {
- if value, ok := p[priority]; ok {
- if v, ok := value.(int64); ok {
- return v
- }
- }
-
- return 10
-}
diff --git a/plugins/jobs/pipeline/pipeline_test.go b/plugins/jobs/pipeline/pipeline_test.go
deleted file mode 100644
index 4482c70d..00000000
--- a/plugins/jobs/pipeline/pipeline_test.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package pipeline
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestPipeline_String(t *testing.T) {
- pipe := Pipeline{"value": "value"}
-
- assert.Equal(t, "value", pipe.String("value", ""))
- assert.Equal(t, "value", pipe.String("other", "value"))
-}
-
-func TestPipeline_Has(t *testing.T) {
- pipe := Pipeline{"options": map[string]interface{}{"ttl": 10}}
-
- assert.Equal(t, true, pipe.Has("options"))
- assert.Equal(t, false, pipe.Has("other"))
-}
diff --git a/plugins/jobs/plugin.go b/plugins/jobs/plugin.go
deleted file mode 100644
index 3aec6acc..00000000
--- a/plugins/jobs/plugin.go
+++ /dev/null
@@ -1,719 +0,0 @@
-package jobs
-
-import (
- "context"
- "fmt"
- "sync"
- "time"
-
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/jobs"
- "github.com/spiral/roadrunner/v2/pkg/events"
- "github.com/spiral/roadrunner/v2/pkg/payload"
- "github.com/spiral/roadrunner/v2/pkg/pool"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- jobState "github.com/spiral/roadrunner/v2/pkg/state/job"
- "github.com/spiral/roadrunner/v2/pkg/state/process"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/server"
-)
-
-const (
- // RrMode env variable
- RrMode string = "RR_MODE"
- RrModeJobs string = "jobs"
-
- PluginName string = "jobs"
- pipelines string = "pipelines"
-)
-
-type Plugin struct {
- sync.RWMutex
-
- // Jobs plugin configuration
- cfg *Config `structure:"jobs"`
- log logger.Logger
- workersPool pool.Pool
- server server.Server
-
- jobConstructors map[string]jobs.Constructor
- consumers sync.Map // map[string]jobs.Consumer
-
- // events handler
- events events.Handler
-
- // priority queue implementation
- queue priorityqueue.Queue
-
- // parent config for broken options. keys are pipelines names, values - pointers to the associated pipeline
- pipelines sync.Map
-
- // initial set of the pipelines to consume
- consume map[string]struct{}
-
- // signal channel to stop the pollers
- stopCh chan struct{}
-
- // internal payloads pool
- pldPool sync.Pool
- statsExporter *statsExporter
-}
-
-func (p *Plugin) Init(cfg config.Configurer, log logger.Logger, server server.Server) error {
- const op = errors.Op("jobs_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(PluginName, &p.cfg)
- if err != nil {
- return errors.E(op, err)
- }
-
- p.cfg.InitDefaults()
-
- p.server = server
-
- p.events = events.NewEventsHandler()
- p.events.AddListener(p.collectJobsEvents)
-
- p.jobConstructors = make(map[string]jobs.Constructor)
- p.consume = make(map[string]struct{})
- p.stopCh = make(chan struct{}, 1)
-
- p.pldPool = sync.Pool{New: func() interface{} {
- // with nil fields
- return &payload.Payload{}
- }}
-
- // initial set of pipelines
- for i := range p.cfg.Pipelines {
- p.pipelines.Store(i, p.cfg.Pipelines[i])
- }
-
- if len(p.cfg.Consume) > 0 {
- for i := 0; i < len(p.cfg.Consume); i++ {
- p.consume[p.cfg.Consume[i]] = struct{}{}
- }
- }
-
- // initialize priority queue
- p.queue = priorityqueue.NewBinHeap(p.cfg.PipelineSize)
- p.log = log
-
- // metrics
- p.statsExporter = newStatsExporter(p)
- p.events.AddListener(p.statsExporter.metricsCallback)
-
- return nil
-}
-
-func (p *Plugin) Serve() chan error { //nolint:gocognit
- errCh := make(chan error, 1)
- const op = errors.Op("jobs_plugin_serve")
-
- // register initial pipelines
- p.pipelines.Range(func(key, value interface{}) bool {
- t := time.Now()
- // pipeline name (ie test-local, sqs-aws, etc)
- name := key.(string)
-
- // pipeline associated with the name
- pipe := value.(*pipeline.Pipeline)
- // driver for the pipeline (ie amqp, ephemeral, etc)
- dr := pipe.Driver()
-
- // jobConstructors contains constructors for the drivers
- // we need here to initialize these drivers for the pipelines
- if _, ok := p.jobConstructors[dr]; ok {
- // config key for the particular sub-driver jobs.pipelines.test-local
- configKey := fmt.Sprintf("%s.%s.%s", PluginName, pipelines, name)
-
- // init the driver
- initializedDriver, err := p.jobConstructors[dr].JobsConstruct(configKey, p.events, p.queue)
- if err != nil {
- errCh <- errors.E(op, err)
- return false
- }
-
- // add driver to the set of the consumers (name - pipeline name, value - associated driver)
- p.consumers.Store(name, initializedDriver)
-
- // register pipeline for the initialized driver
- err = initializedDriver.Register(context.Background(), pipe)
- if err != nil {
- errCh <- errors.E(op, errors.Errorf("pipe register failed for the driver: %s with pipe name: %s", pipe.Driver(), pipe.Name()))
- return false
- }
-
- // if pipeline initialized to be consumed, call Run on it
- if _, ok := p.consume[name]; ok {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(p.cfg.Timeout))
- defer cancel()
- err = initializedDriver.Run(ctx, pipe)
- if err != nil {
- errCh <- errors.E(op, err)
- return false
- }
- return true
- }
-
- return true
- }
-
- p.events.Push(events.JobEvent{
- Event: events.EventDriverReady,
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Start: t,
- Elapsed: t.Sub(t),
- })
-
- return true
- })
-
- // do not continue processing, immediately stop if channel contains an error
- if len(errCh) > 0 {
- return errCh
- }
-
- var err error
- p.workersPool, err = p.server.NewWorkerPool(context.Background(), p.cfg.Pool, map[string]string{RrMode: RrModeJobs})
- if err != nil {
- errCh <- err
- return errCh
- }
-
- // start listening
- go func() {
- for i := uint8(0); i < p.cfg.NumPollers; i++ {
- go func() {
- for {
- select {
- case <-p.stopCh:
- p.log.Info("------> job poller stopped <------")
- return
- default:
- // get prioritized JOB from the queue
- jb := p.queue.ExtractMin()
-
- // parse the context
- // for each job, context contains:
- /*
- 1. Job class
- 2. Job ID provided from the outside
- 3. Job Headers map[string][]string
- 4. Timeout in seconds
- 5. Pipeline name
- */
-
- start := time.Now()
- p.events.Push(events.JobEvent{
- Event: events.EventJobStart,
- ID: jb.ID(),
- Start: start,
- Elapsed: 0,
- })
-
- ctx, err := jb.Context()
- if err != nil {
- p.events.Push(events.JobEvent{
- Event: events.EventJobError,
- Error: err,
- ID: jb.ID(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- errNack := jb.Nack()
- if errNack != nil {
- p.log.Error("negatively acknowledge failed", "error", errNack)
- }
- p.log.Error("job marshal context", "error", err)
- continue
- }
-
- // get payload from the sync.Pool
- exec := p.getPayload(jb.Body(), ctx)
-
- // protect from the pool reset
- p.RLock()
- resp, err := p.workersPool.Exec(exec)
- p.RUnlock()
- if err != nil {
- p.events.Push(events.JobEvent{
- Event: events.EventJobError,
- ID: jb.ID(),
- Error: err,
- Start: start,
- Elapsed: time.Since(start),
- })
- // RR protocol level error, Nack the job
- errNack := jb.Nack()
- if errNack != nil {
- p.log.Error("negatively acknowledge failed", "error", errNack)
- }
-
- p.log.Error("job execute failed", "error", err)
-
- p.putPayload(exec)
- continue
- }
-
- // if response is nil or body is nil, just acknowledge the job
- if resp == nil || resp.Body == nil {
- p.putPayload(exec)
- err = jb.Ack()
- if err != nil {
- p.events.Push(events.JobEvent{
- Event: events.EventJobError,
- ID: jb.ID(),
- Error: err,
- Start: start,
- Elapsed: time.Since(start),
- })
- p.log.Error("acknowledge error, job might be missed", "error", err)
- continue
- }
-
- p.events.Push(events.JobEvent{
- Event: events.EventJobOK,
- ID: jb.ID(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- continue
- }
-
- // handle the response protocol
- err = handleResponse(resp.Body, jb, p.log)
- if err != nil {
- p.events.Push(events.JobEvent{
- Event: events.EventJobError,
- ID: jb.ID(),
- Start: start,
- Error: err,
- Elapsed: time.Since(start),
- })
- p.putPayload(exec)
- errNack := jb.Nack()
- if errNack != nil {
- p.log.Error("negatively acknowledge failed, job might be lost", "root error", err, "error nack", errNack)
- continue
- }
-
- p.log.Error("job negatively acknowledged", "error", err)
- continue
- }
-
- p.events.Push(events.JobEvent{
- Event: events.EventJobOK,
- ID: jb.ID(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- // return payload
- p.putPayload(exec)
- }
- }
- }()
- }
- }()
-
- return errCh
-}
-
-func (p *Plugin) Stop() error {
- // range over all consumers and call stop
- p.consumers.Range(func(key, value interface{}) bool {
- consumer := value.(jobs.Consumer)
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(p.cfg.Timeout))
- err := consumer.Stop(ctx)
- if err != nil {
- cancel()
- p.log.Error("stop job driver", "driver", key)
- return true
- }
- cancel()
- return true
- })
-
- // this function can block forever, but we don't care, because we might have a chance to exit from the pollers,
- // but if not, this is not a problem at all.
- // The main target is to stop the drivers
- go func() {
- for i := uint8(0); i < p.cfg.NumPollers; i++ {
- // stop jobs plugin pollers
- p.stopCh <- struct{}{}
- }
- }()
-
- // just wait pollers for 5 seconds before exit
- time.Sleep(time.Second * 5)
-
- p.Lock()
- p.workersPool.Destroy(context.Background())
- p.Unlock()
-
- return nil
-}
-
-func (p *Plugin) Collects() []interface{} {
- return []interface{}{
- p.CollectMQBrokers,
- }
-}
-
-func (p *Plugin) CollectMQBrokers(name endure.Named, c jobs.Constructor) {
- p.jobConstructors[name.Name()] = c
-}
-
-func (p *Plugin) Workers() []*process.State {
- p.RLock()
- wrk := p.workersPool.Workers()
- p.RUnlock()
-
- ps := make([]*process.State, len(wrk))
-
- for i := 0; i < len(wrk); i++ {
- st, err := process.WorkerProcessState(wrk[i])
- if err != nil {
- p.log.Error("jobs workers state", "error", err)
- return nil
- }
-
- ps[i] = st
- }
-
- return ps
-}
-
-func (p *Plugin) JobsState(ctx context.Context) ([]*jobState.State, error) {
- const op = errors.Op("jobs_plugin_drivers_state")
- jst := make([]*jobState.State, 0, 2)
- var err error
- p.consumers.Range(func(key, value interface{}) bool {
- consumer := value.(jobs.Consumer)
- newCtx, cancel := context.WithTimeout(ctx, time.Second*time.Duration(p.cfg.Timeout))
-
- var state *jobState.State
- state, err = consumer.State(newCtx)
- if err != nil {
- cancel()
- return false
- }
-
- jst = append(jst, state)
- cancel()
- return true
- })
-
- if err != nil {
- return nil, errors.E(op, err)
- }
- return jst, nil
-}
-
-func (p *Plugin) Available() {}
-
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-func (p *Plugin) Reset() error {
- p.Lock()
- defer p.Unlock()
-
- const op = errors.Op("jobs_plugin_reset")
- p.log.Info("JOBS plugin received restart request. Restarting...")
- p.workersPool.Destroy(context.Background())
- p.workersPool = nil
-
- var err error
- p.workersPool, err = p.server.NewWorkerPool(context.Background(), p.cfg.Pool, map[string]string{RrMode: RrModeJobs}, p.collectJobsEvents, p.statsExporter.metricsCallback)
- if err != nil {
- return errors.E(op, err)
- }
-
- p.log.Info("JOBS workers pool successfully restarted")
-
- return nil
-}
-
-func (p *Plugin) Push(j *job.Job) error {
- const op = errors.Op("jobs_plugin_push")
-
- start := time.Now()
- // get the pipeline for the job
- pipe, ok := p.pipelines.Load(j.Options.Pipeline)
- if !ok {
- return errors.E(op, errors.Errorf("no such pipeline, requested: %s", j.Options.Pipeline))
- }
-
- // type conversion
- ppl := pipe.(*pipeline.Pipeline)
-
- d, ok := p.consumers.Load(ppl.Name())
- if !ok {
- return errors.E(op, errors.Errorf("consumer not registered for the requested driver: %s", ppl.Driver()))
- }
-
- // if job has no priority, inherit it from the pipeline
- if j.Options.Priority == 0 {
- j.Options.Priority = ppl.Priority()
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(p.cfg.Timeout))
- defer cancel()
-
- err := d.(jobs.Consumer).Push(ctx, j)
- if err != nil {
- p.events.Push(events.JobEvent{
- Event: events.EventPushError,
- ID: j.Ident,
- Pipeline: ppl.Name(),
- Driver: ppl.Driver(),
- Error: err,
- Start: start,
- Elapsed: time.Since(start),
- })
- return errors.E(op, err)
- }
-
- p.events.Push(events.JobEvent{
- Event: events.EventPushOK,
- ID: j.Ident,
- Pipeline: ppl.Name(),
- Driver: ppl.Driver(),
- Error: err,
- Start: start,
- Elapsed: time.Since(start),
- })
-
- return nil
-}
-
-func (p *Plugin) PushBatch(j []*job.Job) error {
- const op = errors.Op("jobs_plugin_push")
- start := time.Now()
-
- for i := 0; i < len(j); i++ {
- // get the pipeline for the job
- pipe, ok := p.pipelines.Load(j[i].Options.Pipeline)
- if !ok {
- return errors.E(op, errors.Errorf("no such pipeline, requested: %s", j[i].Options.Pipeline))
- }
-
- ppl := pipe.(*pipeline.Pipeline)
-
- d, ok := p.consumers.Load(ppl.Name())
- if !ok {
- return errors.E(op, errors.Errorf("consumer not registered for the requested driver: %s", ppl.Driver()))
- }
-
- // if job has no priority, inherit it from the pipeline
- if j[i].Options.Priority == 0 {
- j[i].Options.Priority = ppl.Priority()
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(p.cfg.Timeout))
- err := d.(jobs.Consumer).Push(ctx, j[i])
- if err != nil {
- cancel()
- p.events.Push(events.JobEvent{
- Event: events.EventPushError,
- ID: j[i].Ident,
- Pipeline: ppl.Name(),
- Driver: ppl.Driver(),
- Start: start,
- Elapsed: time.Since(start),
- Error: err,
- })
- return errors.E(op, err)
- }
-
- cancel()
- }
-
- return nil
-}
-
-func (p *Plugin) Pause(pp string) {
- pipe, ok := p.pipelines.Load(pp)
-
- if !ok {
- p.log.Error("no such pipeline", "requested", pp)
- }
-
- ppl := pipe.(*pipeline.Pipeline)
-
- d, ok := p.consumers.Load(ppl.Name())
- if !ok {
- p.log.Warn("driver for the pipeline not found", "pipeline", pp)
- return
- }
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(p.cfg.Timeout))
- defer cancel()
- // redirect call to the underlying driver
- d.(jobs.Consumer).Pause(ctx, ppl.Name())
-}
-
-func (p *Plugin) Resume(pp string) {
- pipe, ok := p.pipelines.Load(pp)
- if !ok {
- p.log.Error("no such pipeline", "requested", pp)
- }
-
- ppl := pipe.(*pipeline.Pipeline)
-
- d, ok := p.consumers.Load(ppl.Name())
- if !ok {
- p.log.Warn("driver for the pipeline not found", "pipeline", pp)
- return
- }
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(p.cfg.Timeout))
- defer cancel()
- // redirect call to the underlying driver
- d.(jobs.Consumer).Resume(ctx, ppl.Name())
-}
-
-// Declare a pipeline.
-func (p *Plugin) Declare(pipeline *pipeline.Pipeline) error {
- const op = errors.Op("jobs_plugin_declare")
- // driver for the pipeline (ie amqp, ephemeral, etc)
- dr := pipeline.Driver()
- if dr == "" {
- return errors.E(op, errors.Errorf("no associated driver with the pipeline, pipeline name: %s", pipeline.Name()))
- }
-
- // jobConstructors contains constructors for the drivers
- // we need here to initialize these drivers for the pipelines
- if _, ok := p.jobConstructors[dr]; ok {
- // init the driver from pipeline
- initializedDriver, err := p.jobConstructors[dr].FromPipeline(pipeline, p.events, p.queue)
- if err != nil {
- return errors.E(op, err)
- }
-
- // register pipeline for the initialized driver
- err = initializedDriver.Register(context.Background(), pipeline)
- if err != nil {
- return errors.E(op, errors.Errorf("pipe register failed for the driver: %s with pipe name: %s", pipeline.Driver(), pipeline.Name()))
- }
-
- // if pipeline initialized to be consumed, call Run on it
- // but likely for the dynamic pipelines it should be started manually
- if _, ok := p.consume[pipeline.Name()]; ok {
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(p.cfg.Timeout))
- defer cancel()
- err = initializedDriver.Run(ctx, pipeline)
- if err != nil {
- return errors.E(op, err)
- }
- }
-
- // add driver to the set of the consumers (name - pipeline name, value - associated driver)
- p.consumers.Store(pipeline.Name(), initializedDriver)
- // save the pipeline
- p.pipelines.Store(pipeline.Name(), pipeline)
- }
-
- return nil
-}
-
-// Destroy pipeline and release all associated resources.
-func (p *Plugin) Destroy(pp string) error {
- const op = errors.Op("jobs_plugin_destroy")
- pipe, ok := p.pipelines.Load(pp)
- if !ok {
- return errors.E(op, errors.Errorf("no such pipeline, requested: %s", pp))
- }
-
- // type conversion
- ppl := pipe.(*pipeline.Pipeline)
-
- // delete consumer
- d, ok := p.consumers.LoadAndDelete(ppl.Name())
- if !ok {
- return errors.E(op, errors.Errorf("consumer not registered for the requested driver: %s", ppl.Driver()))
- }
-
- // delete old pipeline
- p.pipelines.LoadAndDelete(pp)
-
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(p.cfg.Timeout))
- err := d.(jobs.Consumer).Stop(ctx)
- if err != nil {
- cancel()
- return errors.E(op, err)
- }
-
- cancel()
- return nil
-}
-
-func (p *Plugin) List() []string {
- out := make([]string, 0, 10)
-
- p.pipelines.Range(func(key, _ interface{}) bool {
- // we can safely convert value here as we know that we store keys as strings
- out = append(out, key.(string))
- return true
- })
-
- return out
-}
-
-func (p *Plugin) RPC() interface{} {
- return &rpc{
- log: p.log,
- p: p,
- }
-}
-
-func (p *Plugin) collectJobsEvents(event interface{}) {
- if jev, ok := event.(events.JobEvent); ok {
- switch jev.Event {
- case events.EventPipePaused:
- p.log.Info("pipeline paused", "pipeline", jev.Pipeline, "driver", jev.Driver, "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventJobStart:
- p.log.Info("job processing started", "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventJobOK:
- p.log.Info("job processed without errors", "ID", jev.ID, "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventPushOK:
- p.log.Info("job pushed to the queue", "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventPushError:
- p.log.Error("job push error, job might be lost", "error", jev.Error, "pipeline", jev.Pipeline, "ID", jev.ID, "driver", jev.Driver, "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventJobError:
- p.log.Error("job processed with errors", "error", jev.Error, "ID", jev.ID, "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventPipeActive:
- p.log.Info("pipeline active", "pipeline", jev.Pipeline, "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventPipeStopped:
- p.log.Warn("pipeline stopped", "pipeline", jev.Pipeline, "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventPipeError:
- p.log.Error("pipeline error", "pipeline", jev.Pipeline, "error", jev.Error, "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- case events.EventDriverReady:
- p.log.Info("driver ready", "pipeline", jev.Pipeline, "start", jev.Start.UTC(), "elapsed", jev.Elapsed)
- }
- }
-}
-
-func (p *Plugin) getPayload(body, context []byte) *payload.Payload {
- pld := p.pldPool.Get().(*payload.Payload)
- pld.Body = body
- pld.Context = context
- return pld
-}
-
-func (p *Plugin) putPayload(pld *payload.Payload) {
- pld.Body = nil
- pld.Context = nil
- p.pldPool.Put(pld)
-}
diff --git a/plugins/jobs/protocol.go b/plugins/jobs/protocol.go
deleted file mode 100644
index 9d769fdf..00000000
--- a/plugins/jobs/protocol.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package jobs
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/errors"
- pq "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-type Type uint32
-
-const (
- NoError Type = iota
- Error
-)
-
-// internal worker protocol (jobs mode)
-type protocol struct {
- // message type, see Type
- T Type `json:"type"`
- // Payload
- Data json.RawMessage `json:"data"`
-}
-
-type errorResp struct {
- Msg string `json:"message"`
- Requeue bool `json:"requeue"`
- Delay int64 `json:"delay_seconds"`
- Headers map[string][]string `json:"headers"`
-}
-
-func handleResponse(resp []byte, jb pq.Item, log logger.Logger) error {
- const op = errors.Op("jobs_handle_response")
- // TODO(rustatian) to sync.Pool
- p := &protocol{}
-
- err := json.Unmarshal(resp, p)
- if err != nil {
- return errors.E(op, err)
- }
-
- switch p.T {
- // likely case
- case NoError:
- err = jb.Ack()
- if err != nil {
- return errors.E(op, err)
- }
- case Error:
- // TODO(rustatian) to sync.Pool
- er := &errorResp{}
-
- err = json.Unmarshal(p.Data, er)
- if err != nil {
- return errors.E(op, err)
- }
-
- log.Error("jobs protocol error", "error", er.Msg, "delay", er.Delay, "requeue", er.Requeue)
-
- if er.Requeue {
- err = jb.Requeue(er.Headers, er.Delay)
- if err != nil {
- return errors.E(op, err)
- }
- return nil
- }
-
- return errors.E(op, errors.Errorf("jobs response error: %v", er.Msg))
-
- default:
- err = jb.Ack()
- if err != nil {
- return errors.E(op, err)
- }
- }
-
- return nil
-}
diff --git a/plugins/jobs/rpc.go b/plugins/jobs/rpc.go
deleted file mode 100644
index d7b93bd1..00000000
--- a/plugins/jobs/rpc.go
+++ /dev/null
@@ -1,160 +0,0 @@
-package jobs
-
-import (
- "context"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- jobsv1beta "github.com/spiral/roadrunner/v2/proto/jobs/v1beta"
-)
-
-type rpc struct {
- log logger.Logger
- p *Plugin
-}
-
-func (r *rpc) Push(j *jobsv1beta.PushRequest, _ *jobsv1beta.Empty) error {
- const op = errors.Op("rpc_push")
-
- // convert transport entity into domain
- // how we can do this quickly
-
- if j.GetJob().GetId() == "" {
- return errors.E(op, errors.Str("empty ID field not allowed"))
- }
-
- err := r.p.Push(from(j.GetJob()))
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (r *rpc) PushBatch(j *jobsv1beta.PushBatchRequest, _ *jobsv1beta.Empty) error {
- const op = errors.Op("rpc_push_batch")
-
- l := len(j.GetJobs())
-
- batch := make([]*job.Job, l)
-
- for i := 0; i < l; i++ {
- // convert transport entity into domain
- // how we can do this quickly
- batch[i] = from(j.GetJobs()[i])
- }
-
- err := r.p.PushBatch(batch)
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (r *rpc) Pause(req *jobsv1beta.Pipelines, _ *jobsv1beta.Empty) error {
- for i := 0; i < len(req.GetPipelines()); i++ {
- r.p.Pause(req.GetPipelines()[i])
- }
-
- return nil
-}
-
-func (r *rpc) Resume(req *jobsv1beta.Pipelines, _ *jobsv1beta.Empty) error {
- for i := 0; i < len(req.GetPipelines()); i++ {
- r.p.Resume(req.GetPipelines()[i])
- }
-
- return nil
-}
-
-func (r *rpc) List(_ *jobsv1beta.Empty, resp *jobsv1beta.Pipelines) error {
- resp.Pipelines = r.p.List()
- return nil
-}
-
-// Declare pipeline used to dynamically declare any type of the pipeline
-// Mandatory fields:
-// 1. Driver
-// 2. Pipeline name
-// 3. Options related to the particular pipeline
-func (r *rpc) Declare(req *jobsv1beta.DeclareRequest, _ *jobsv1beta.Empty) error {
- const op = errors.Op("rpc_declare_pipeline")
- pipe := &pipeline.Pipeline{}
-
- for i := range req.GetPipeline() {
- (*pipe)[i] = req.GetPipeline()[i]
- }
-
- err := r.p.Declare(pipe)
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (r *rpc) Destroy(req *jobsv1beta.Pipelines, resp *jobsv1beta.Pipelines) error {
- const op = errors.Op("rpc_declare_pipeline")
-
- var destroyed []string //nolint:prealloc
- for i := 0; i < len(req.GetPipelines()); i++ {
- err := r.p.Destroy(req.GetPipelines()[i])
- if err != nil {
- return errors.E(op, err)
- }
- destroyed = append(destroyed, req.GetPipelines()[i])
- }
-
- // return destroyed pipelines
- resp.Pipelines = destroyed
-
- return nil
-}
-
-func (r *rpc) Stat(_ *jobsv1beta.Empty, resp *jobsv1beta.Stats) error {
- const op = errors.Op("rpc_stats")
- state, err := r.p.JobsState(context.Background())
- if err != nil {
- return errors.E(op, err)
- }
-
- for i := 0; i < len(state); i++ {
- resp.Stats = append(resp.Stats, &jobsv1beta.Stat{
- Pipeline: state[i].Pipeline,
- Driver: state[i].Driver,
- Queue: state[i].Queue,
- Active: state[i].Active,
- Delayed: state[i].Delayed,
- Reserved: state[i].Reserved,
- Ready: state[i].Ready,
- })
- }
-
- return nil
-}
-
-// from converts from transport entity to domain
-func from(j *jobsv1beta.Job) *job.Job {
- headers := make(map[string][]string, len(j.GetHeaders()))
-
- for k, v := range j.GetHeaders() {
- headers[k] = v.GetValue()
- }
-
- jb := &job.Job{
- Job: j.GetJob(),
- Headers: headers,
- Ident: j.GetId(),
- Payload: j.GetPayload(),
- Options: &job.Options{
- Priority: j.GetOptions().GetPriority(),
- Pipeline: j.GetOptions().GetPipeline(),
- Delay: j.GetOptions().GetDelay(),
- },
- }
-
- return jb
-}
diff --git a/plugins/kv/config.go b/plugins/kv/config.go
deleted file mode 100644
index 09ba79cd..00000000
--- a/plugins/kv/config.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package kv
-
-// Config represents general storage configuration with keys as the user defined kv-names and values as the constructors
-type Config struct {
- Data map[string]interface{} `mapstructure:"kv"`
-}
diff --git a/plugins/kv/doc/kv.drawio b/plugins/kv/doc/kv.drawio
deleted file mode 100644
index 04470e4a..00000000
--- a/plugins/kv/doc/kv.drawio
+++ /dev/null
@@ -1 +0,0 @@
-<mxfile host="Electron" modified="2021-04-22T21:31:28.320Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.5.1 Chrome/89.0.4389.128 Electron/12.0.5 Safari/537.36" etag="PMNN2QoTRBeugwC1WCGf" version="14.5.1" type="device"><diagram id="2us8W0xnLog_cmX3fgYy" name="Page-1">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</diagram></mxfile> \ No newline at end of file
diff --git a/plugins/kv/plugin.go b/plugins/kv/plugin.go
deleted file mode 100644
index 86bd982f..00000000
--- a/plugins/kv/plugin.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package kv
-
-import (
- "fmt"
-
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/kv"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const (
- // PluginName linked to the memory, boltdb, memcached, redis plugins. DO NOT change w/o sync.
- PluginName string = "kv"
- // driver is the mandatory field which should present in every storage
- driver string = "driver"
- // config key used to detect local configuration for the driver
- cfg string = "config"
-)
-
-// Plugin for the unified storage
-type Plugin struct {
- log logger.Logger
- // constructors contains general storage constructors, such as boltdb, memory, memcached, redis.
- constructors map[string]kv.Constructor
- // storages contains user-defined storages, such as boltdb-north, memcached-us and so on.
- storages map[string]kv.Storage
- // KV configuration
- cfg Config
- cfgPlugin config.Configurer
-}
-
-func (p *Plugin) Init(cfg config.Configurer, log logger.Logger) error {
- const op = errors.Op("kv_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(PluginName, &p.cfg.Data)
- if err != nil {
- return errors.E(op, err)
- }
- p.constructors = make(map[string]kv.Constructor, 5)
- p.storages = make(map[string]kv.Storage, 5)
- p.log = log
- p.cfgPlugin = cfg
- return nil
-}
-
-func (p *Plugin) Serve() chan error {
- errCh := make(chan error, 1)
- const op = errors.Op("kv_plugin_serve")
- // key - storage name in the config
- // value - storage
- // For this config we should have 3 constructors: memory, boltdb and memcached but 4 KVs: default, boltdb-south, boltdb-north and memcached
- // when user requests for example boltdb-south, we should provide that particular preconfigured storage
-
- for k, v := range p.cfg.Data {
- // for example if the key not properly formatted (yaml)
- if v == nil {
- continue
- }
-
- // check type of the v
- // should be a map[string]interface{}
- switch t := v.(type) {
- // correct type
- case map[string]interface{}:
- if _, ok := t[driver]; !ok {
- errCh <- errors.E(op, errors.Errorf("could not find mandatory driver field in the %s storage", k))
- return errCh
- }
- default:
- p.log.Warn("wrong type detected in the configuration, please, check yaml indentation")
- continue
- }
-
- // config key for the particular sub-driver kv.memcached.config
- configKey := fmt.Sprintf("%s.%s.%s", PluginName, k, cfg)
- // at this point we know, that driver field present in the configuration
- drName := v.(map[string]interface{})[driver]
-
- // driver name should be a string
- if drStr, ok := drName.(string); ok {
- switch {
- // local configuration section key
- case p.cfgPlugin.Has(configKey):
- if _, ok := p.constructors[drStr]; !ok {
- p.log.Warn("no constructors registered", "requested constructor", drStr, "registered", p.constructors)
- continue
- }
-
- storage, err := p.constructors[drStr].KVConstruct(configKey)
- if err != nil {
- errCh <- errors.E(op, err)
- return errCh
- }
-
- // save the storage
- p.storages[k] = storage
- // try global then
- case p.cfgPlugin.Has(k):
- if _, ok := p.constructors[drStr]; !ok {
- p.log.Warn("no constructors registered", "requested constructor", drStr, "registered", p.constructors)
- continue
- }
-
- // use only key for the driver registration, for example rr-boltdb should be globally available
- storage, err := p.constructors[drStr].KVConstruct(k)
- if err != nil {
- errCh <- errors.E(op, err)
- return errCh
- }
-
- // save the storage
- p.storages[k] = storage
- default:
- p.log.Error("can't find local or global configuration, this section will be skipped", "local: ", configKey, "global: ", k)
- continue
- }
- }
- continue
- }
-
- return errCh
-}
-
-func (p *Plugin) Stop() error {
- // stop all attached storages
- for k := range p.storages {
- p.storages[k].Stop()
- }
- return nil
-}
-
-// Collects will get all plugins which implement Storage interface
-func (p *Plugin) Collects() []interface{} {
- return []interface{}{
- p.GetAllStorageDrivers,
- }
-}
-
-func (p *Plugin) GetAllStorageDrivers(name endure.Named, constructor kv.Constructor) {
- // save the storage constructor
- p.constructors[name.Name()] = constructor
-}
-
-// RPC returns associated rpc service.
-func (p *Plugin) RPC() interface{} {
- return &rpc{srv: p, log: p.log, storages: p.storages}
-}
-
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (p *Plugin) Available() {}
diff --git a/plugins/kv/rpc.go b/plugins/kv/rpc.go
deleted file mode 100644
index ad4aefa9..00000000
--- a/plugins/kv/rpc.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package kv
-
-import (
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/kv"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- kvv1 "github.com/spiral/roadrunner/v2/proto/kv/v1beta"
-)
-
-// Wrapper for the plugin
-type rpc struct {
- // all available storages
- storages map[string]kv.Storage
- // svc is a plugin implementing Storage interface
- srv *Plugin
- // Logger
- log logger.Logger
-}
-
-// Has accept []*kvv1.Payload proto payload with Storage and Item
-func (r *rpc) Has(in *kvv1.Request, out *kvv1.Response) error {
- const op = errors.Op("rpc_has")
-
- if in.GetStorage() == "" {
- return errors.E(op, errors.Str("no storage provided"))
- }
-
- keys := make([]string, 0, len(in.GetItems()))
-
- for i := 0; i < len(in.GetItems()); i++ {
- keys = append(keys, in.Items[i].Key)
- }
-
- if st, ok := r.storages[in.GetStorage()]; ok {
- ret, err := st.Has(keys...)
- if err != nil {
- return errors.E(op, err)
- }
-
- // update the value in the pointer
- // save the result
- out.Items = make([]*kvv1.Item, 0, len(ret))
- for k := range ret {
- out.Items = append(out.Items, &kvv1.Item{
- Key: k,
- })
- }
- return nil
- }
-
- return errors.E(op, errors.Errorf("no such storage: %s", in.GetStorage()))
-}
-
-// Set accept proto payload with Storage and Item
-func (r *rpc) Set(in *kvv1.Request, _ *kvv1.Response) error {
- const op = errors.Op("rpc_set")
-
- if st, exists := r.storages[in.GetStorage()]; exists {
- err := st.Set(in.GetItems()...)
- if err != nil {
- return errors.E(op, err)
- }
-
- // save the result
- return nil
- }
-
- return errors.E(op, errors.Errorf("no such storage: %s", in.GetStorage()))
-}
-
-// MGet accept proto payload with Storage and Item
-func (r *rpc) MGet(in *kvv1.Request, out *kvv1.Response) error {
- const op = errors.Op("rpc_mget")
-
- keys := make([]string, 0, len(in.GetItems()))
-
- for i := 0; i < len(in.GetItems()); i++ {
- keys = append(keys, in.Items[i].Key)
- }
-
- if st, exists := r.storages[in.GetStorage()]; exists {
- ret, err := st.MGet(keys...)
- if err != nil {
- return errors.E(op, err)
- }
-
- out.Items = make([]*kvv1.Item, 0, len(ret))
- for k := range ret {
- out.Items = append(out.Items, &kvv1.Item{
- Key: k,
- Value: ret[k],
- })
- }
- return nil
- }
-
- return errors.E(op, errors.Errorf("no such storage: %s", in.GetStorage()))
-}
-
-// MExpire accept proto payload with Storage and Item
-func (r *rpc) MExpire(in *kvv1.Request, _ *kvv1.Response) error {
- const op = errors.Op("rpc_mexpire")
-
- if st, exists := r.storages[in.GetStorage()]; exists {
- err := st.MExpire(in.GetItems()...)
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
- }
-
- return errors.E(op, errors.Errorf("no such storage: %s", in.GetStorage()))
-}
-
-// TTL accept proto payload with Storage and Item
-func (r *rpc) TTL(in *kvv1.Request, out *kvv1.Response) error {
- const op = errors.Op("rpc_ttl")
- keys := make([]string, 0, len(in.GetItems()))
-
- for i := 0; i < len(in.GetItems()); i++ {
- keys = append(keys, in.Items[i].Key)
- }
-
- if st, exists := r.storages[in.GetStorage()]; exists {
- ret, err := st.TTL(keys...)
- if err != nil {
- return errors.E(op, err)
- }
-
- out.Items = make([]*kvv1.Item, 0, len(ret))
- for k := range ret {
- out.Items = append(out.Items, &kvv1.Item{
- Key: k,
- Timeout: ret[k],
- })
- }
-
- return nil
- }
-
- return errors.E(op, errors.Errorf("no such storage: %s", in.GetStorage()))
-}
-
-// Delete accept proto payload with Storage and Item
-func (r *rpc) Delete(in *kvv1.Request, _ *kvv1.Response) error {
- const op = errors.Op("rcp_delete")
-
- keys := make([]string, 0, len(in.GetItems()))
-
- for i := 0; i < len(in.GetItems()); i++ {
- keys = append(keys, in.Items[i].Key)
- }
- if st, exists := r.storages[in.GetStorage()]; exists {
- err := st.Delete(keys...)
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
- }
-
- return errors.E(op, errors.Errorf("no such storage: %s", in.GetStorage()))
-}
-
-// Clear clean the storage
-func (r *rpc) Clear(in *kvv1.Request, _ *kvv1.Response) error {
- const op = errors.Op("rcp_delete")
-
- if st, exists := r.storages[in.GetStorage()]; exists {
- err := st.Clear()
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
- }
-
- return errors.E(op, errors.Errorf("no such storage: %s", in.GetStorage()))
-}
diff --git a/plugins/logger/config.go b/plugins/logger/config.go
deleted file mode 100644
index 6ef56661..00000000
--- a/plugins/logger/config.go
+++ /dev/null
@@ -1,212 +0,0 @@
-package logger
-
-import (
- "os"
- "strings"
-
- "go.uber.org/zap"
- "go.uber.org/zap/zapcore"
- "gopkg.in/natefinch/lumberjack.v2"
-)
-
-// ChannelConfig configures loggers per channel.
-type ChannelConfig struct {
- // Dedicated channels per logger. By default logger allocated via named logger.
- Channels map[string]Config `mapstructure:"channels"`
-}
-
-// FileLoggerConfig structure represents configuration for the file logger
-type FileLoggerConfig struct {
- // Filename is the file to write logs to. Backup log files will be retained
- // in the same directory. It uses <processname>-lumberjack.log in
- // os.TempDir() if empty.
- LogOutput string `mapstructure:"log_output"`
-
- // MaxSize is the maximum size in megabytes of the log file before it gets
- // rotated. It defaults to 100 megabytes.
- MaxSize int `mapstructure:"max_size"`
-
- // MaxAge is the maximum number of days to retain old log files based on the
- // timestamp encoded in their filename. Note that a day is defined as 24
- // hours and may not exactly correspond to calendar days due to daylight
- // savings, leap seconds, etc. The default is not to remove old log files
- // based on age.
- MaxAge int `mapstructure:"max_age"`
-
- // MaxBackups is the maximum number of old log files to retain. The default
- // is to retain all old log files (though MaxAge may still cause them to get
- // deleted.)
- MaxBackups int `mapstructure:"max_backups"`
-
- // Compress determines if the rotated log files should be compressed
- // using gzip. The default is not to perform compression.
- Compress bool `mapstructure:"compress"`
-}
-
-func (fl *FileLoggerConfig) InitDefaults() *FileLoggerConfig {
- if fl.LogOutput == "" {
- fl.LogOutput = os.TempDir()
- }
-
- if fl.MaxSize == 0 {
- fl.MaxSize = 100
- }
-
- if fl.MaxAge == 0 {
- fl.MaxAge = 24
- }
-
- if fl.MaxBackups == 0 {
- fl.MaxBackups = 10
- }
-
- return fl
-}
-
-type Config struct {
- // Mode configures logger based on some default template (development, production, off).
- Mode Mode `mapstructure:"mode"`
-
- // Level is the minimum enabled logging level. Note that this is a dynamic
- // level, so calling ChannelConfig.Level.SetLevel will atomically change the log
- // level of all loggers descended from this config.
- Level string `mapstructure:"level"`
-
- // Encoding sets the logger's encoding. InitDefault values are "json" and
- // "console", as well as any third-party encodings registered via
- // RegisterEncoder.
- Encoding string `mapstructure:"encoding"`
-
- // Output is a list of URLs or file paths to write logging output to.
- // See Open for details.
- Output []string `mapstructure:"output"`
-
- // ErrorOutput is a list of URLs to write internal logger errors to.
- // The default is standard error.
- //
- // Note that this setting only affects internal errors; for sample code that
- // sends error-level logs to a different location from info- and debug-level
- // logs, see the package-level AdvancedConfiguration example.
- ErrorOutput []string `mapstructure:"errorOutput"`
-
- // File logger options
- FileLogger *FileLoggerConfig `mapstructure:"file_logger_options"`
-}
-
-// BuildLogger converts config into Zap configuration.
-func (cfg *Config) BuildLogger() (*zap.Logger, error) {
- var zCfg zap.Config
- switch Mode(strings.ToLower(string(cfg.Mode))) {
- case off, none:
- return zap.NewNop(), nil
- case production:
- zCfg = zap.NewProductionConfig()
- case development:
- zCfg = zap.Config{
- Level: zap.NewAtomicLevelAt(zap.DebugLevel),
- Development: true,
- Encoding: "console",
- EncoderConfig: zapcore.EncoderConfig{
- // Keys can be anything except the empty string.
- TimeKey: "T",
- LevelKey: "L",
- NameKey: "N",
- CallerKey: "C",
- FunctionKey: zapcore.OmitKey,
- MessageKey: "M",
- StacktraceKey: "S",
- LineEnding: zapcore.DefaultLineEnding,
- EncodeLevel: ColoredLevelEncoder,
- EncodeTime: zapcore.ISO8601TimeEncoder,
- EncodeDuration: zapcore.StringDurationEncoder,
- EncodeCaller: zapcore.ShortCallerEncoder,
- EncodeName: ColoredNameEncoder,
- },
- OutputPaths: []string{"stderr"},
- ErrorOutputPaths: []string{"stderr"},
- }
- case raw:
- zCfg = zap.Config{
- Level: zap.NewAtomicLevelAt(zap.InfoLevel),
- Encoding: "console",
- EncoderConfig: zapcore.EncoderConfig{
- MessageKey: "message",
- },
- OutputPaths: []string{"stderr"},
- ErrorOutputPaths: []string{"stderr"},
- }
- default:
- zCfg = zap.Config{
- Level: zap.NewAtomicLevelAt(zap.DebugLevel),
- Encoding: "console",
- EncoderConfig: zapcore.EncoderConfig{
- MessageKey: "message",
- LevelKey: "level",
- TimeKey: "time",
- NameKey: "name",
- EncodeName: ColoredHashedNameEncoder,
- EncodeLevel: ColoredLevelEncoder,
- EncodeTime: UTCTimeEncoder,
- EncodeCaller: zapcore.ShortCallerEncoder,
- },
- OutputPaths: []string{"stderr"},
- ErrorOutputPaths: []string{"stderr"},
- }
- }
-
- if cfg.Level != "" {
- level := zap.NewAtomicLevel()
- if err := level.UnmarshalText([]byte(cfg.Level)); err == nil {
- zCfg.Level = level
- }
- }
-
- if cfg.Encoding != "" {
- zCfg.Encoding = cfg.Encoding
- }
-
- if len(cfg.Output) != 0 {
- zCfg.OutputPaths = cfg.Output
- }
-
- if len(cfg.ErrorOutput) != 0 {
- zCfg.ErrorOutputPaths = cfg.ErrorOutput
- }
-
- // if we also have a file logger specified in the config
- // init it
- // otherwise - return standard config
- if cfg.FileLogger != nil {
- // init absent options
- cfg.FileLogger.InitDefaults()
-
- w := zapcore.AddSync(
- &lumberjack.Logger{
- Filename: cfg.FileLogger.LogOutput,
- MaxSize: cfg.FileLogger.MaxSize,
- MaxAge: cfg.FileLogger.MaxAge,
- MaxBackups: cfg.FileLogger.MaxBackups,
- Compress: cfg.FileLogger.Compress,
- },
- )
-
- core := zapcore.NewCore(
- zapcore.NewJSONEncoder(zCfg.EncoderConfig),
- w,
- zCfg.Level,
- )
- return zap.New(core), nil
- }
-
- return zCfg.Build()
-}
-
-// InitDefault Initialize default logger
-func (cfg *Config) InitDefault() {
- if cfg.Mode == "" {
- cfg.Mode = development
- }
- if cfg.Level == "" {
- cfg.Level = "debug"
- }
-}
diff --git a/plugins/logger/encoder.go b/plugins/logger/encoder.go
deleted file mode 100644
index 4ff583c4..00000000
--- a/plugins/logger/encoder.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package logger
-
-import (
- "hash/fnv"
- "strings"
- "time"
-
- "github.com/fatih/color"
- "go.uber.org/zap/zapcore"
-)
-
-var colorMap = []func(string, ...interface{}) string{
- color.HiYellowString,
- color.HiGreenString,
- color.HiBlueString,
- color.HiRedString,
- color.HiCyanString,
- color.HiMagentaString,
-}
-
-// ColoredLevelEncoder colorizes log levels.
-func ColoredLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
- switch level {
- case zapcore.DebugLevel:
- enc.AppendString(color.HiWhiteString(level.CapitalString()))
- case zapcore.InfoLevel:
- enc.AppendString(color.HiCyanString(level.CapitalString()))
- case zapcore.WarnLevel:
- enc.AppendString(color.HiYellowString(level.CapitalString()))
- case zapcore.ErrorLevel, zapcore.DPanicLevel:
- enc.AppendString(color.HiRedString(level.CapitalString()))
- case zapcore.PanicLevel, zapcore.FatalLevel:
- enc.AppendString(color.HiMagentaString(level.CapitalString()))
- }
-}
-
-// ColoredNameEncoder colorizes service names.
-func ColoredNameEncoder(s string, enc zapcore.PrimitiveArrayEncoder) {
- if len(s) < 12 {
- s += strings.Repeat(" ", 12-len(s))
- }
-
- enc.AppendString(color.HiGreenString(s))
-}
-
-// ColoredHashedNameEncoder colorizes service names and assigns different colors to different names.
-func ColoredHashedNameEncoder(s string, enc zapcore.PrimitiveArrayEncoder) {
- if len(s) < 12 {
- s += strings.Repeat(" ", 12-len(s))
- }
-
- colorID := stringHash(s, len(colorMap))
- enc.AppendString(colorMap[colorID](s))
-}
-
-// UTCTimeEncoder encodes time into short UTC specific timestamp.
-func UTCTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
- enc.AppendString(t.UTC().Format("2006/01/02 15:04:05"))
-}
-
-// returns string hash
-func stringHash(name string, base int) int {
- h := fnv.New32a()
- _, _ = h.Write([]byte(name))
- return int(h.Sum32()) % base
-}
diff --git a/plugins/logger/enums.go b/plugins/logger/enums.go
deleted file mode 100644
index 803eace0..00000000
--- a/plugins/logger/enums.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package logger
-
-// Mode represents available logger modes
-type Mode string
-
-const (
- none Mode = "none"
- off Mode = "off"
- production Mode = "production"
- development Mode = "development"
- raw Mode = "raw"
-)
diff --git a/plugins/logger/interface.go b/plugins/logger/interface.go
deleted file mode 100644
index 827f9821..00000000
--- a/plugins/logger/interface.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package logger
-
-// Logger is an general RR log interface
-type Logger interface {
- Debug(msg string, keyvals ...interface{})
- Info(msg string, keyvals ...interface{})
- Warn(msg string, keyvals ...interface{})
- Error(msg string, keyvals ...interface{})
-}
-
-// WithLogger creates a child logger and adds structured context to it
-type WithLogger interface {
- With(keyvals ...interface{}) Logger
-}
diff --git a/plugins/logger/plugin.go b/plugins/logger/plugin.go
deleted file mode 100644
index ffbf7f5e..00000000
--- a/plugins/logger/plugin.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package logger
-
-import (
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "go.uber.org/zap"
-)
-
-// PluginName declares plugin name.
-const PluginName = "logs"
-
-// ZapLogger manages zap logger.
-type ZapLogger struct {
- base *zap.Logger
- cfg *Config
- channels ChannelConfig
-}
-
-// Init logger service.
-func (z *ZapLogger) Init(cfg config.Configurer) error {
- const op = errors.Op("config_plugin_init")
- var err error
- // if not configured, configure with default params
- if !cfg.Has(PluginName) {
- z.cfg = &Config{}
- z.cfg.InitDefault()
-
- z.base, err = z.cfg.BuildLogger()
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
-
- return nil
- }
-
- err = cfg.UnmarshalKey(PluginName, &z.cfg)
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
-
- err = cfg.UnmarshalKey(PluginName, &z.channels)
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
-
- z.base, err = z.cfg.BuildLogger()
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
- return nil
-}
-
-// NamedLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params.
-func (z *ZapLogger) NamedLogger(name string) (Logger, error) {
- if cfg, ok := z.channels.Channels[name]; ok {
- l, err := cfg.BuildLogger()
- if err != nil {
- return nil, err
- }
- return NewZapAdapter(l.Named(name)), nil
- }
-
- return NewZapAdapter(z.base.Named(name)), nil
-}
-
-// ServiceLogger returns logger dedicated to the specific channel. Similar to Named() but also reads the core params.
-func (z *ZapLogger) ServiceLogger(n endure.Named) (Logger, error) {
- return z.NamedLogger(n.Name())
-}
-
-// Provides declares factory methods.
-func (z *ZapLogger) Provides() []interface{} {
- return []interface{}{
- z.ServiceLogger,
- }
-}
-
-// Name returns user-friendly plugin name
-func (z *ZapLogger) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (z *ZapLogger) Available() {
-}
diff --git a/plugins/logger/std_log_adapter.go b/plugins/logger/std_log_adapter.go
deleted file mode 100644
index 479aa565..00000000
--- a/plugins/logger/std_log_adapter.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package logger
-
-import (
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-// StdLogAdapter can be passed to the http.Server or any place which required standard logger to redirect output
-// to the logger plugin
-type StdLogAdapter struct {
- log Logger
-}
-
-// Write io.Writer interface implementation
-func (s *StdLogAdapter) Write(p []byte) (n int, err error) {
- s.log.Error("server internal error", "message", utils.AsString(p))
- return len(p), nil
-}
-
-// NewStdAdapter constructs StdLogAdapter
-func NewStdAdapter(log Logger) *StdLogAdapter {
- logAdapter := &StdLogAdapter{
- log: log,
- }
-
- return logAdapter
-}
diff --git a/plugins/logger/zap_adapter.go b/plugins/logger/zap_adapter.go
deleted file mode 100644
index 1c68cf25..00000000
--- a/plugins/logger/zap_adapter.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package logger
-
-import (
- "fmt"
-
- "go.uber.org/zap"
- core "go.uber.org/zap/zapcore"
-)
-
-type ZapAdapter struct {
- zl *zap.Logger
-}
-
-// NewZapAdapter ... which uses general log interface
-func NewZapAdapter(zapLogger *zap.Logger) *ZapAdapter {
- return &ZapAdapter{
- zl: zapLogger.WithOptions(zap.AddCallerSkip(1)),
- }
-}
-
-func separateFields(keyVals []interface{}) ([]zap.Field, []interface{}) {
- var fields []zap.Field
- var pairedKeyVals []interface{}
-
- for key := range keyVals {
- switch value := keyVals[key].(type) {
- case zap.Field:
- fields = append(fields, value)
- case core.ObjectMarshaler:
- fields = append(fields, zap.Inline(value))
- default:
- pairedKeyVals = append(pairedKeyVals, value)
- }
- }
- return fields, pairedKeyVals
-}
-
-func (log *ZapAdapter) fields(keyvals []interface{}) []zap.Field {
- // separate any zap fields from other structs
- zapFields, keyvals := separateFields(keyvals)
-
- // we should have even number of keys and values
- if len(keyvals)%2 != 0 {
- return []zap.Field{zap.Error(fmt.Errorf("odd number of keyvals pairs: %v", keyvals))}
- }
-
- fields := make([]zap.Field, 0, len(keyvals)/2+len(zapFields))
- for i := 0; i < len(keyvals); i += 2 {
- key, ok := keyvals[i].(string)
- if !ok {
- key = fmt.Sprintf("%v", keyvals[i])
- }
- fields = append(fields, zap.Any(key, keyvals[i+1]))
- }
- // add all the fields
- fields = append(fields, zapFields...)
-
- return fields
-}
-
-func (log *ZapAdapter) Debug(msg string, keyvals ...interface{}) {
- log.zl.Debug(msg, log.fields(keyvals)...)
-}
-
-func (log *ZapAdapter) Info(msg string, keyvals ...interface{}) {
- log.zl.Info(msg, log.fields(keyvals)...)
-}
-
-func (log *ZapAdapter) Warn(msg string, keyvals ...interface{}) {
- log.zl.Warn(msg, log.fields(keyvals)...)
-}
-
-func (log *ZapAdapter) Error(msg string, keyvals ...interface{}) {
- log.zl.Error(msg, log.fields(keyvals)...)
-}
-
-func (log *ZapAdapter) With(keyvals ...interface{}) Logger {
- return NewZapAdapter(log.zl.With(log.fields(keyvals)...))
-}
diff --git a/plugins/memcached/memcachedkv/config.go b/plugins/memcached/memcachedkv/config.go
deleted file mode 100644
index 569e2573..00000000
--- a/plugins/memcached/memcachedkv/config.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package memcachedkv
-
-type Config struct {
- // Addr is url for memcached, 11211 port is used by default
- Addr []string
-}
-
-func (s *Config) InitDefaults() {
- if s.Addr == nil {
- s.Addr = []string{"127.0.0.1:11211"} // default url for memcached
- }
-}
diff --git a/plugins/memcached/memcachedkv/driver.go b/plugins/memcached/memcachedkv/driver.go
deleted file mode 100644
index dcb071b4..00000000
--- a/plugins/memcached/memcachedkv/driver.go
+++ /dev/null
@@ -1,254 +0,0 @@
-package memcachedkv
-
-import (
- "strings"
- "time"
-
- "github.com/bradfitz/gomemcache/memcache"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- kvv1 "github.com/spiral/roadrunner/v2/proto/kv/v1beta"
-)
-
-type Driver struct {
- client *memcache.Client
- log logger.Logger
- cfg *Config
-}
-
-// NewMemcachedDriver returns a memcache client using the provided server(s)
-// with equal weight. If a server is listed multiple times,
-// it gets a proportional amount of weight.
-func NewMemcachedDriver(log logger.Logger, key string, cfgPlugin config.Configurer) (*Driver, error) {
- const op = errors.Op("new_memcached_driver")
-
- s := &Driver{
- log: log,
- }
-
- err := cfgPlugin.UnmarshalKey(key, &s.cfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- if s.cfg == nil {
- return nil, errors.E(op, errors.Errorf("config not found by provided key: %s", key))
- }
-
- s.cfg.InitDefaults()
-
- m := memcache.New(s.cfg.Addr...)
- s.client = m
-
- return s, nil
-}
-
-// Has checks the key for existence
-func (d *Driver) Has(keys ...string) (map[string]bool, error) {
- const op = errors.Op("memcached_plugin_has")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
- m := make(map[string]bool, len(keys))
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- exist, err := d.client.Get(keys[i])
-
- if err != nil {
- // ErrCacheMiss means that a Get failed because the item wasn't present.
- if err == memcache.ErrCacheMiss {
- continue
- }
- return nil, errors.E(op, err)
- }
- if exist != nil {
- m[keys[i]] = true
- }
- }
- return m, nil
-}
-
-// Get gets the item for the given key. ErrCacheMiss is returned for a
-// memcache cache miss. The key must be at most 250 bytes in length.
-func (d *Driver) Get(key string) ([]byte, error) {
- const op = errors.Op("memcached_plugin_get")
- // to get cases like " "
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- data, err := d.client.Get(key)
- if err != nil {
- // ErrCacheMiss means that a Get failed because the item wasn't present.
- if err == memcache.ErrCacheMiss {
- return nil, nil
- }
- return nil, errors.E(op, err)
- }
- if data != nil {
- // return the value by the key
- return data.Value, nil
- }
- // data is nil by some reason and error also nil
- return nil, nil
-}
-
-// MGet return map with key -- string
-// and map value as value -- []byte
-func (d *Driver) MGet(keys ...string) (map[string][]byte, error) {
- const op = errors.Op("memcached_plugin_mget")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- }
-
- m := make(map[string][]byte, len(keys))
- for i := range keys {
- // Here also MultiGet
- data, err := d.client.Get(keys[i])
- if err != nil {
- // ErrCacheMiss means that a Get failed because the item wasn't present.
- if err == memcache.ErrCacheMiss {
- continue
- }
- return nil, errors.E(op, err)
- }
- if data != nil {
- m[keys[i]] = data.Value
- }
- }
-
- return m, nil
-}
-
-// Set sets the KV pairs. Keys should be 250 bytes maximum
-// TTL:
-// Expiration is the cache expiration time, in seconds: either a relative
-// time from now (up to 1 month), or an absolute Unix epoch time.
-// Zero means the Item has no expiration time.
-func (d *Driver) Set(items ...*kvv1.Item) error {
- const op = errors.Op("memcached_plugin_set")
- if items == nil {
- return errors.E(op, errors.NoKeys)
- }
-
- for i := range items {
- if items[i] == nil {
- return errors.E(op, errors.EmptyItem)
- }
-
- // pre-allocate item
- memcachedItem := &memcache.Item{
- Key: items[i].Key,
- // unsafe convert
- Value: items[i].Value,
- Flags: 0,
- }
-
- // add additional TTL in case of TTL isn't empty
- if items[i].Timeout != "" {
- // verify the TTL
- t, err := time.Parse(time.RFC3339, items[i].Timeout)
- if err != nil {
- return err
- }
- memcachedItem.Expiration = int32(t.Unix())
- }
-
- err := d.client.Set(memcachedItem)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// MExpire Expiration is the cache expiration time, in seconds: either a relative
-// time from now (up to 1 month), or an absolute Unix epoch time.
-// Zero means the Item has no expiration time.
-func (d *Driver) MExpire(items ...*kvv1.Item) error {
- const op = errors.Op("memcached_plugin_mexpire")
- for i := range items {
- if items[i] == nil {
- continue
- }
- if items[i].Timeout == "" || strings.TrimSpace(items[i].Key) == "" {
- return errors.E(op, errors.Str("should set timeout and at least one key"))
- }
-
- // verify provided TTL
- t, err := time.Parse(time.RFC3339, items[i].Timeout)
- if err != nil {
- return errors.E(op, err)
- }
-
- // Touch updates the expiry for the given key. The seconds parameter is either
- // a Unix timestamp or, if seconds is less than 1 month, the number of seconds
- // into the future at which time the item will expire. Zero means the item has
- // no expiration time. ErrCacheMiss is returned if the key is not in the cache.
- // The key must be at most 250 bytes in length.
- err = d.client.Touch(items[i].Key, int32(t.Unix()))
- if err != nil {
- return errors.E(op, err)
- }
- }
- return nil
-}
-
-// TTL return time in seconds (int32) for a given keys
-func (d *Driver) TTL(_ ...string) (map[string]string, error) {
- const op = errors.Op("memcached_plugin_ttl")
- return nil, errors.E(op, errors.Str("not valid request for memcached, see https://github.com/memcached/memcached/issues/239"))
-}
-
-func (d *Driver) Delete(keys ...string) error {
- const op = errors.Op("memcached_plugin_has")
- if keys == nil {
- return errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return errors.E(op, errors.EmptyKey)
- }
- }
-
- for i := range keys {
- err := d.client.Delete(keys[i])
- // ErrCacheMiss means that a Get failed because the item wasn't present.
- if err != nil {
- // ErrCacheMiss means that a Get failed because the item wasn't present.
- if err == memcache.ErrCacheMiss {
- continue
- }
- return errors.E(op, err)
- }
- }
- return nil
-}
-
-func (d *Driver) Clear() error {
- err := d.client.DeleteAll()
- if err != nil {
- d.log.Error("flush_all operation failed", "error", err)
- return err
- }
-
- return nil
-}
-
-func (d *Driver) Stop() {}
diff --git a/plugins/memcached/plugin.go b/plugins/memcached/plugin.go
deleted file mode 100644
index 47bca0e2..00000000
--- a/plugins/memcached/plugin.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package memcached
-
-import (
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/kv"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/memcached/memcachedkv"
-)
-
-const (
- PluginName string = "memcached"
- RootPluginName string = "kv"
-)
-
-type Plugin struct {
- // config plugin
- cfgPlugin config.Configurer
- // logger
- log logger.Logger
-}
-
-func (s *Plugin) Init(log logger.Logger, cfg config.Configurer) error {
- if !cfg.Has(RootPluginName) {
- return errors.E(errors.Disabled)
- }
-
- s.cfgPlugin = cfg
- s.log = log
- return nil
-}
-
-// Name returns plugin user-friendly name
-func (s *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (s *Plugin) Available() {}
-
-func (s *Plugin) KVConstruct(key string) (kv.Storage, error) {
- const op = errors.Op("boltdb_plugin_provide")
- st, err := memcachedkv.NewMemcachedDriver(s.log, key, s.cfgPlugin)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return st, nil
-}
diff --git a/plugins/memory/memoryjobs/consumer.go b/plugins/memory/memoryjobs/consumer.go
deleted file mode 100644
index 79246063..00000000
--- a/plugins/memory/memoryjobs/consumer.go
+++ /dev/null
@@ -1,296 +0,0 @@
-package memoryjobs
-
-import (
- "context"
- "sync/atomic"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- jobState "github.com/spiral/roadrunner/v2/pkg/state/job"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-const (
- prefetch string = "prefetch"
- goroutinesMax uint64 = 1000
-)
-
-type Config struct {
- Prefetch uint64 `mapstructure:"prefetch"`
-}
-
-type consumer struct {
- cfg *Config
- log logger.Logger
- eh events.Handler
- pipeline atomic.Value
- pq priorityqueue.Queue
- localPrefetch chan *Item
-
- // time.sleep goroutines max number
- goroutines uint64
-
- delayed *int64
- active *int64
-
- listeners uint32
- stopCh chan struct{}
-}
-
-func NewJobBroker(configKey string, log logger.Logger, cfg config.Configurer, eh events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("new_ephemeral_pipeline")
-
- jb := &consumer{
- log: log,
- pq: pq,
- eh: eh,
- goroutines: 0,
- active: utils.Int64(0),
- delayed: utils.Int64(0),
- stopCh: make(chan struct{}),
- }
-
- err := cfg.UnmarshalKey(configKey, &jb.cfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- if jb.cfg == nil {
- return nil, errors.E(op, errors.Errorf("config not found by provided key: %s", configKey))
- }
-
- if jb.cfg.Prefetch == 0 {
- jb.cfg.Prefetch = 100_000
- }
-
- // initialize a local queue
- jb.localPrefetch = make(chan *Item, jb.cfg.Prefetch)
-
- return jb, nil
-}
-
-func FromPipeline(pipeline *pipeline.Pipeline, log logger.Logger, eh events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- return &consumer{
- log: log,
- pq: pq,
- eh: eh,
- localPrefetch: make(chan *Item, pipeline.Int(prefetch, 100_000)),
- goroutines: 0,
- active: utils.Int64(0),
- delayed: utils.Int64(0),
- stopCh: make(chan struct{}),
- }, nil
-}
-
-func (c *consumer) Push(ctx context.Context, jb *job.Job) error {
- const op = errors.Op("ephemeral_push")
-
- // check if the pipeline registered
- _, ok := c.pipeline.Load().(*pipeline.Pipeline)
- if !ok {
- return errors.E(op, errors.Errorf("no such pipeline: %s", jb.Options.Pipeline))
- }
-
- err := c.handleItem(ctx, fromJob(jb))
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (c *consumer) State(_ context.Context) (*jobState.State, error) {
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- return &jobState.State{
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Queue: pipe.Name(),
- Active: atomic.LoadInt64(c.active),
- Delayed: atomic.LoadInt64(c.delayed),
- Ready: ready(atomic.LoadUint32(&c.listeners)),
- }, nil
-}
-
-func (c *consumer) Register(_ context.Context, pipeline *pipeline.Pipeline) error {
- c.pipeline.Store(pipeline)
- return nil
-}
-
-func (c *consumer) Run(_ context.Context, pipe *pipeline.Pipeline) error {
- const op = errors.Op("memory_jobs_run")
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: time.Now(),
- })
-
- l := atomic.LoadUint32(&c.listeners)
- // listener already active
- if l == 1 {
- c.log.Warn("listener already in the active state")
- return errors.E(op, errors.Str("listener already in the active state"))
- }
-
- c.consume()
- atomic.StoreUint32(&c.listeners, 1)
-
- return nil
-}
-
-func (c *consumer) Pause(_ context.Context, p string) {
- start := time.Now()
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- c.log.Error("no such pipeline", "requested pause on: ", p)
- }
-
- l := atomic.LoadUint32(&c.listeners)
- // no active listeners
- if l == 0 {
- c.log.Warn("no active listeners, nothing to pause")
- return
- }
-
- atomic.AddUint32(&c.listeners, ^uint32(0))
-
- // stop the consumer
- c.stopCh <- struct{}{}
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipePaused,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (c *consumer) Resume(_ context.Context, p string) {
- start := time.Now()
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- c.log.Error("no such pipeline", "requested resume on: ", p)
- }
-
- l := atomic.LoadUint32(&c.listeners)
- // listener already active
- if l == 1 {
- c.log.Warn("listener already in the active state")
- return
- }
-
- // resume the consumer on the same channel
- c.consume()
-
- atomic.StoreUint32(&c.listeners, 1)
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (c *consumer) Stop(_ context.Context) error {
- start := time.Now()
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
-
- select {
- case c.stopCh <- struct{}{}:
- default:
- break
- }
-
- for i := 0; i < len(c.localPrefetch); i++ {
- // drain all jobs from the channel
- <-c.localPrefetch
- }
-
- c.localPrefetch = nil
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeStopped,
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- return nil
-}
-
-func (c *consumer) handleItem(ctx context.Context, msg *Item) error {
- const op = errors.Op("ephemeral_handle_request")
- // handle timeouts
- // theoretically, some bad user may send millions requests with a delay and produce a billion (for example)
- // goroutines here. We should limit goroutines here.
- if msg.Options.Delay > 0 {
- // if we have 1000 goroutines waiting on the delay - reject 1001
- if atomic.LoadUint64(&c.goroutines) >= goroutinesMax {
- return errors.E(op, errors.Str("max concurrency number reached"))
- }
-
- go func(jj *Item) {
- atomic.AddUint64(&c.goroutines, 1)
- atomic.AddInt64(c.delayed, 1)
-
- time.Sleep(jj.Options.DelayDuration())
-
- select {
- case c.localPrefetch <- jj:
- atomic.AddUint64(&c.goroutines, ^uint64(0))
- default:
- c.log.Warn("can't push job", "error", "local queue closed or full")
- }
- }(msg)
-
- return nil
- }
-
- // increase number of the active jobs
- atomic.AddInt64(c.active, 1)
-
- // insert to the local, limited pipeline
- select {
- case c.localPrefetch <- msg:
- return nil
- case <-ctx.Done():
- return errors.E(op, errors.Errorf("local pipeline is full, consider to increase prefetch number, current limit: %d, context error: %v", c.cfg.Prefetch, ctx.Err()))
- }
-}
-
-func (c *consumer) consume() {
- go func() {
- // redirect
- for {
- select {
- case item, ok := <-c.localPrefetch:
- if !ok {
- c.log.Warn("ephemeral local prefetch queue closed")
- return
- }
-
- // set requeue channel
- item.Options.requeueFn = c.handleItem
- item.Options.active = c.active
- item.Options.delayed = c.delayed
-
- c.pq.Insert(item)
- case <-c.stopCh:
- return
- }
- }
- }()
-}
-
-func ready(r uint32) bool {
- return r > 0
-}
diff --git a/plugins/memory/memoryjobs/item.go b/plugins/memory/memoryjobs/item.go
deleted file mode 100644
index f4d62ada..00000000
--- a/plugins/memory/memoryjobs/item.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package memoryjobs
-
-import (
- "context"
- "sync/atomic"
- "time"
-
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-type Item struct {
- // Job contains name of job broker (usually PHP class).
- Job string `json:"job"`
-
- // Ident is unique identifier of the job, should be provided from outside
- Ident string `json:"id"`
-
- // Payload is string data (usually JSON) passed to Job broker.
- Payload string `json:"payload"`
-
- // Headers with key-values pairs
- Headers map[string][]string `json:"headers"`
-
- // Options contains set of PipelineOptions specific to job execution. Can be empty.
- Options *Options `json:"options,omitempty"`
-}
-
-// Options carry information about how to handle given job.
-type Options struct {
- // Priority is job priority, default - 10
- // pointer to distinguish 0 as a priority and nil as priority not set
- Priority int64 `json:"priority"`
-
- // Pipeline manually specified pipeline.
- Pipeline string `json:"pipeline,omitempty"`
-
- // Delay defines time duration to delay execution for. Defaults to none.
- Delay int64 `json:"delay,omitempty"`
-
- // private
- requeueFn func(context.Context, *Item) error
- active *int64
- delayed *int64
-}
-
-// DelayDuration returns delay duration in a form of time.Duration.
-func (o *Options) DelayDuration() time.Duration {
- return time.Second * time.Duration(o.Delay)
-}
-
-func (i *Item) ID() string {
- return i.Ident
-}
-
-func (i *Item) Priority() int64 {
- return i.Options.Priority
-}
-
-// Body packs job payload into binary payload.
-func (i *Item) Body() []byte {
- return utils.AsBytes(i.Payload)
-}
-
-// Context packs job context (job, id) into binary payload.
-func (i *Item) Context() ([]byte, error) {
- ctx, err := json.Marshal(
- struct {
- ID string `json:"id"`
- Job string `json:"job"`
- Headers map[string][]string `json:"headers"`
- Pipeline string `json:"pipeline"`
- }{ID: i.Ident, Job: i.Job, Headers: i.Headers, Pipeline: i.Options.Pipeline},
- )
-
- if err != nil {
- return nil, err
- }
-
- return ctx, nil
-}
-
-func (i *Item) Ack() error {
- i.atomicallyReduceCount()
- return nil
-}
-
-func (i *Item) Nack() error {
- i.atomicallyReduceCount()
- return nil
-}
-
-func (i *Item) Requeue(headers map[string][]string, delay int64) error {
- // overwrite the delay
- i.Options.Delay = delay
- i.Headers = headers
-
- i.atomicallyReduceCount()
-
- err := i.Options.requeueFn(context.Background(), i)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// atomicallyReduceCount reduces counter of active or delayed jobs
-func (i *Item) atomicallyReduceCount() {
- // if job was delayed, reduce number of the delayed jobs
- if i.Options.Delay > 0 {
- atomic.AddInt64(i.Options.delayed, ^int64(0))
- return
- }
-
- // otherwise, reduce number of the active jobs
- atomic.AddInt64(i.Options.active, ^int64(0))
- // noop for the in-memory
-}
-
-func fromJob(job *job.Job) *Item {
- return &Item{
- Job: job.Job,
- Ident: job.Ident,
- Payload: job.Payload,
- Headers: job.Headers,
- Options: &Options{
- Priority: job.Options.Priority,
- Pipeline: job.Options.Pipeline,
- Delay: job.Options.Delay,
- },
- }
-}
diff --git a/plugins/memory/memorykv/config.go b/plugins/memory/memorykv/config.go
deleted file mode 100644
index a8a8993f..00000000
--- a/plugins/memory/memorykv/config.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package memorykv
-
-// Config is default config for the in-memory driver
-type Config struct {
- // Interval for the check
- Interval int
-}
-
-// InitDefaults by default driver is turned off
-func (c *Config) InitDefaults() {
- if c.Interval == 0 {
- c.Interval = 60 // seconds
- }
-}
diff --git a/plugins/memory/memorykv/kv.go b/plugins/memory/memorykv/kv.go
deleted file mode 100644
index 5383275c..00000000
--- a/plugins/memory/memorykv/kv.go
+++ /dev/null
@@ -1,257 +0,0 @@
-package memorykv
-
-import (
- "strings"
- "sync"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- kvv1 "github.com/spiral/roadrunner/v2/proto/kv/v1beta"
-)
-
-type Driver struct {
- clearMu sync.RWMutex
- heap sync.Map
- // stop is used to stop keys GC and close boltdb connection
- stop chan struct{}
- log logger.Logger
- cfg *Config
-}
-
-func NewInMemoryDriver(key string, log logger.Logger, cfgPlugin config.Configurer) (*Driver, error) {
- const op = errors.Op("new_in_memory_driver")
-
- d := &Driver{
- stop: make(chan struct{}),
- log: log,
- }
-
- err := cfgPlugin.UnmarshalKey(key, &d.cfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- if d.cfg == nil {
- return nil, errors.E(op, errors.Errorf("config not found by provided key: %s", key))
- }
-
- d.cfg.InitDefaults()
-
- go d.gc()
-
- return d, nil
-}
-
-func (d *Driver) Has(keys ...string) (map[string]bool, error) {
- const op = errors.Op("in_memory_plugin_has")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
- m := make(map[string]bool)
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
-
- if _, ok := d.heap.Load(keys[i]); ok {
- m[keys[i]] = true
- }
- }
-
- return m, nil
-}
-
-func (d *Driver) Get(key string) ([]byte, error) {
- const op = errors.Op("in_memory_plugin_get")
- // to get cases like " "
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
-
- if data, exist := d.heap.Load(key); exist {
- // here might be a panic
- // but data only could be a string, see Set function
- return data.(*kvv1.Item).Value, nil
- }
- return nil, nil
-}
-
-func (d *Driver) MGet(keys ...string) (map[string][]byte, error) {
- const op = errors.Op("in_memory_plugin_mget")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- }
-
- m := make(map[string][]byte, len(keys))
-
- for i := range keys {
- if value, ok := d.heap.Load(keys[i]); ok {
- m[keys[i]] = value.(*kvv1.Item).Value
- }
- }
-
- return m, nil
-}
-
-func (d *Driver) Set(items ...*kvv1.Item) error {
- const op = errors.Op("in_memory_plugin_set")
- if items == nil {
- return errors.E(op, errors.NoKeys)
- }
-
- for i := range items {
- if items[i] == nil {
- continue
- }
- // TTL is set
- if items[i].Timeout != "" {
- // check the TTL in the item
- _, err := time.Parse(time.RFC3339, items[i].Timeout)
- if err != nil {
- return err
- }
- }
-
- d.heap.Store(items[i].Key, items[i])
- }
- return nil
-}
-
-// MExpire sets the expiration time to the key
-// If key already has the expiration time, it will be overwritten
-func (d *Driver) MExpire(items ...*kvv1.Item) error {
- const op = errors.Op("in_memory_plugin_mexpire")
- for i := range items {
- if items[i] == nil {
- continue
- }
- if items[i].Timeout == "" || strings.TrimSpace(items[i].Key) == "" {
- return errors.E(op, errors.Str("should set timeout and at least one key"))
- }
-
- // if key exist, overwrite it value
- if pItem, ok := d.heap.LoadAndDelete(items[i].Key); ok {
- // check that time is correct
- _, err := time.Parse(time.RFC3339, items[i].Timeout)
- if err != nil {
- return errors.E(op, err)
- }
- tmp := pItem.(*kvv1.Item)
- // guess that t is in the future
- // in memory is just FOR TESTING PURPOSES
- // LOGIC ISN'T IDEAL
- d.heap.Store(items[i].Key, &kvv1.Item{
- Key: items[i].Key,
- Value: tmp.Value,
- Timeout: items[i].Timeout,
- })
- }
- }
-
- return nil
-}
-
-func (d *Driver) TTL(keys ...string) (map[string]string, error) {
- const op = errors.Op("in_memory_plugin_ttl")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- }
-
- m := make(map[string]string, len(keys))
-
- for i := range keys {
- if item, ok := d.heap.Load(keys[i]); ok {
- m[keys[i]] = item.(*kvv1.Item).Timeout
- }
- }
- return m, nil
-}
-
-func (d *Driver) Delete(keys ...string) error {
- const op = errors.Op("in_memory_plugin_delete")
- if keys == nil {
- return errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for i := range keys {
- keyTrimmed := strings.TrimSpace(keys[i])
- if keyTrimmed == "" {
- return errors.E(op, errors.EmptyKey)
- }
- }
-
- for i := range keys {
- d.heap.Delete(keys[i])
- }
- return nil
-}
-
-func (d *Driver) Clear() error {
- d.clearMu.Lock()
- d.heap = sync.Map{}
- d.clearMu.Unlock()
-
- return nil
-}
-
-func (d *Driver) Stop() {
- d.stop <- struct{}{}
-}
-
-// ================================== PRIVATE ======================================
-
-func (d *Driver) gc() {
- ticker := time.NewTicker(time.Duration(d.cfg.Interval) * time.Second)
- defer ticker.Stop()
- for {
- select {
- case <-d.stop:
- return
- case now := <-ticker.C:
- // mutes needed to clear the map
- d.clearMu.RLock()
-
- // check every second
- d.heap.Range(func(key, value interface{}) bool {
- v := value.(*kvv1.Item)
- if v.Timeout == "" {
- return true
- }
-
- t, err := time.Parse(time.RFC3339, v.Timeout)
- if err != nil {
- return false
- }
-
- if now.After(t) {
- d.log.Debug("key deleted", "key", key)
- d.heap.Delete(key)
- }
- return true
- })
-
- d.clearMu.RUnlock()
- }
- }
-}
diff --git a/plugins/memory/memorypubsub/pubsub.go b/plugins/memory/memorypubsub/pubsub.go
deleted file mode 100644
index 231da134..00000000
--- a/plugins/memory/memorypubsub/pubsub.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package memorypubsub
-
-import (
- "context"
- "sync"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/pkg/bst"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-type PubSubDriver struct {
- sync.RWMutex
- // channel with the messages from the RPC
- pushCh chan *pubsub.Message
- // user-subscribed topics
- storage bst.Storage
- log logger.Logger
-}
-
-func NewPubSubDriver(log logger.Logger, _ string) (*PubSubDriver, error) {
- ps := &PubSubDriver{
- pushCh: make(chan *pubsub.Message, 100),
- storage: bst.NewBST(),
- log: log,
- }
- return ps, nil
-}
-
-func (p *PubSubDriver) Publish(msg *pubsub.Message) error {
- p.pushCh <- msg
- return nil
-}
-
-func (p *PubSubDriver) PublishAsync(msg *pubsub.Message) {
- go func() {
- p.pushCh <- msg
- }()
-}
-
-func (p *PubSubDriver) Subscribe(connectionID string, topics ...string) error {
- p.Lock()
- defer p.Unlock()
- for i := 0; i < len(topics); i++ {
- p.storage.Insert(connectionID, topics[i])
- }
- return nil
-}
-
-func (p *PubSubDriver) Unsubscribe(connectionID string, topics ...string) error {
- p.Lock()
- defer p.Unlock()
- for i := 0; i < len(topics); i++ {
- p.storage.Remove(connectionID, topics[i])
- }
- return nil
-}
-
-func (p *PubSubDriver) Connections(topic string, res map[string]struct{}) {
- p.RLock()
- defer p.RUnlock()
-
- ret := p.storage.Get(topic)
- for rr := range ret {
- res[rr] = struct{}{}
- }
-}
-
-func (p *PubSubDriver) Next(ctx context.Context) (*pubsub.Message, error) {
- const op = errors.Op("pubsub_memory")
- select {
- case msg := <-p.pushCh:
- if msg == nil {
- return nil, nil
- }
-
- p.RLock()
- defer p.RUnlock()
- // push only messages, which topics are subscibed
- // TODO better???
- // if we have active subscribers - send a message to a topic
- // or send nil instead
- if ok := p.storage.Contains(msg.Topic); ok {
- return msg, nil
- }
- case <-ctx.Done():
- return nil, errors.E(op, errors.TimeOut, ctx.Err())
- }
-
- return nil, nil
-}
diff --git a/plugins/memory/plugin.go b/plugins/memory/plugin.go
deleted file mode 100644
index 87e0f84b..00000000
--- a/plugins/memory/plugin.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package memory
-
-import (
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/jobs"
- "github.com/spiral/roadrunner/v2/common/kv"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/memory/memoryjobs"
- "github.com/spiral/roadrunner/v2/plugins/memory/memorykv"
- "github.com/spiral/roadrunner/v2/plugins/memory/memorypubsub"
-)
-
-const PluginName string = "memory"
-
-type Plugin struct {
- log logger.Logger
- cfg config.Configurer
-}
-
-func (p *Plugin) Init(log logger.Logger, cfg config.Configurer) error {
- p.log = log
- p.cfg = cfg
- return nil
-}
-
-func (p *Plugin) Serve() chan error {
- return make(chan error, 1)
-}
-
-func (p *Plugin) Stop() error {
- return nil
-}
-
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-func (p *Plugin) Available() {}
-
-// Drivers implementation
-
-func (p *Plugin) PSConstruct(key string) (pubsub.PubSub, error) {
- return memorypubsub.NewPubSubDriver(p.log, key)
-}
-
-func (p *Plugin) KVConstruct(key string) (kv.Storage, error) {
- const op = errors.Op("memory_plugin_construct")
- st, err := memorykv.NewInMemoryDriver(key, p.log, p.cfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
- return st, nil
-}
-
-// JobsConstruct creates new ephemeral consumer from the configuration
-func (p *Plugin) JobsConstruct(configKey string, e events.Handler, pq priorityqueue.Queue) (jobs.Consumer, error) {
- return memoryjobs.NewJobBroker(configKey, p.log, p.cfg, e, pq)
-}
-
-// FromPipeline creates new ephemeral consumer from the provided pipeline
-func (p *Plugin) FromPipeline(pipeline *pipeline.Pipeline, e events.Handler, pq priorityqueue.Queue) (jobs.Consumer, error) {
- return memoryjobs.FromPipeline(pipeline, p.log, e, pq)
-}
diff --git a/plugins/metrics/config.go b/plugins/metrics/config.go
deleted file mode 100644
index a2835130..00000000
--- a/plugins/metrics/config.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package metrics
-
-import (
- "fmt"
-
- "github.com/prometheus/client_golang/prometheus"
-)
-
-// Config configures metrics service.
-type Config struct {
- // Address to listen
- Address string
-
- // Collect define application specific metrics.
- Collect map[string]Collector
-}
-
-type NamedCollector struct {
- // Name of the collector
- Name string `json:"name"`
-
- // Collector structure
- Collector `json:"collector"`
-}
-
-// CollectorType represents prometheus collector types
-type CollectorType string
-
-const (
- // Histogram type
- Histogram CollectorType = "histogram"
-
- // Gauge type
- Gauge CollectorType = "gauge"
-
- // Counter type
- Counter CollectorType = "counter"
-
- // Summary type
- Summary CollectorType = "summary"
-)
-
-// Collector describes single application specific metric.
-type Collector struct {
- // Namespace of the metric.
- Namespace string `json:"namespace"`
- // Subsystem of the metric.
- Subsystem string `json:"subsystem"`
- // Collector type (histogram, gauge, counter, summary).
- Type CollectorType `json:"type"`
- // Help of collector.
- Help string `json:"help"`
- // Labels for vectorized metrics.
- Labels []string `json:"labels"`
- // Buckets for histogram metric.
- Buckets []float64 `json:"buckets"`
- // Objectives for the summary opts
- Objectives map[float64]float64 `json:"objectives"`
-}
-
-// register application specific metrics.
-func (c *Config) getCollectors() (map[string]prometheus.Collector, error) {
- if c.Collect == nil {
- return nil, nil
- }
-
- collectors := make(map[string]prometheus.Collector)
-
- for name, m := range c.Collect {
- var collector prometheus.Collector
- switch m.Type {
- case Histogram:
- opts := prometheus.HistogramOpts{
- Name: name,
- Namespace: m.Namespace,
- Subsystem: m.Subsystem,
- Help: m.Help,
- Buckets: m.Buckets,
- }
-
- if len(m.Labels) != 0 {
- collector = prometheus.NewHistogramVec(opts, m.Labels)
- } else {
- collector = prometheus.NewHistogram(opts)
- }
- case Gauge:
- opts := prometheus.GaugeOpts{
- Name: name,
- Namespace: m.Namespace,
- Subsystem: m.Subsystem,
- Help: m.Help,
- }
-
- if len(m.Labels) != 0 {
- collector = prometheus.NewGaugeVec(opts, m.Labels)
- } else {
- collector = prometheus.NewGauge(opts)
- }
- case Counter:
- opts := prometheus.CounterOpts{
- Name: name,
- Namespace: m.Namespace,
- Subsystem: m.Subsystem,
- Help: m.Help,
- }
-
- if len(m.Labels) != 0 {
- collector = prometheus.NewCounterVec(opts, m.Labels)
- } else {
- collector = prometheus.NewCounter(opts)
- }
- case Summary:
- opts := prometheus.SummaryOpts{
- Name: name,
- Namespace: m.Namespace,
- Subsystem: m.Subsystem,
- Help: m.Help,
- Objectives: m.Objectives,
- }
-
- if len(m.Labels) != 0 {
- collector = prometheus.NewSummaryVec(opts, m.Labels)
- } else {
- collector = prometheus.NewSummary(opts)
- }
- default:
- return nil, fmt.Errorf("invalid metric type `%s` for `%s`", m.Type, name)
- }
-
- collectors[name] = collector
- }
-
- return collectors, nil
-}
-
-func (c *Config) InitDefaults() {
- if c.Address == "" {
- c.Address = "127.0.0.1:2112"
- }
-}
diff --git a/plugins/metrics/config_test.go b/plugins/metrics/config_test.go
deleted file mode 100644
index 665ec9cd..00000000
--- a/plugins/metrics/config_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package metrics
-
-import (
- "bytes"
- "testing"
-
- j "github.com/json-iterator/go"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/stretchr/testify/assert"
-)
-
-var json = j.ConfigCompatibleWithStandardLibrary
-
-func Test_Config_Hydrate_Error1(t *testing.T) {
- cfg := `{"request": {"From": "Something"}}`
- c := &Config{}
- f := new(bytes.Buffer)
- f.WriteString(cfg)
-
- err := json.Unmarshal(f.Bytes(), &c)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Config_Hydrate_Error2(t *testing.T) {
- cfg := `{"dir": "/dir/"`
- c := &Config{}
-
- f := new(bytes.Buffer)
- f.WriteString(cfg)
-
- err := json.Unmarshal(f.Bytes(), &c)
- assert.Error(t, err)
-}
-
-func Test_Config_Metrics(t *testing.T) {
- cfg := `{
-"collect":{
- "metric1":{"type": "gauge"},
- "metric2":{ "type": "counter"},
- "metric3":{"type": "summary"},
- "metric4":{"type": "histogram"}
-}
-}`
- c := &Config{}
- f := new(bytes.Buffer)
- f.WriteString(cfg)
-
- err := json.Unmarshal(f.Bytes(), &c)
- if err != nil {
- t.Fatal(err)
- }
-
- m, err := c.getCollectors()
- assert.NoError(t, err)
-
- assert.IsType(t, prometheus.NewGauge(prometheus.GaugeOpts{}), m["metric1"])
- assert.IsType(t, prometheus.NewCounter(prometheus.CounterOpts{}), m["metric2"])
- assert.IsType(t, prometheus.NewSummary(prometheus.SummaryOpts{}), m["metric3"])
- assert.IsType(t, prometheus.NewHistogram(prometheus.HistogramOpts{}), m["metric4"])
-}
-
-func Test_Config_MetricsVector(t *testing.T) {
- cfg := `{
-"collect":{
- "metric1":{"type": "gauge","labels":["label"]},
- "metric2":{ "type": "counter","labels":["label"]},
- "metric3":{"type": "summary","labels":["label"]},
- "metric4":{"type": "histogram","labels":["label"]}
-}
-}`
- c := &Config{}
- f := new(bytes.Buffer)
- f.WriteString(cfg)
-
- err := json.Unmarshal(f.Bytes(), &c)
- if err != nil {
- t.Fatal(err)
- }
-
- m, err := c.getCollectors()
- assert.NoError(t, err)
-
- assert.IsType(t, prometheus.NewGaugeVec(prometheus.GaugeOpts{}, []string{}), m["metric1"])
- assert.IsType(t, prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{}), m["metric2"])
- assert.IsType(t, prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{}), m["metric3"])
- assert.IsType(t, prometheus.NewHistogramVec(prometheus.HistogramOpts{}, []string{}), m["metric4"])
-}
diff --git a/plugins/metrics/doc.go b/plugins/metrics/doc.go
deleted file mode 100644
index 1abe097a..00000000
--- a/plugins/metrics/doc.go
+++ /dev/null
@@ -1 +0,0 @@
-package metrics
diff --git a/plugins/metrics/interface.go b/plugins/metrics/interface.go
deleted file mode 100644
index 87ba4017..00000000
--- a/plugins/metrics/interface.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package metrics
-
-import "github.com/prometheus/client_golang/prometheus"
-
-type StatProvider interface {
- MetricsCollector() []prometheus.Collector
-}
diff --git a/plugins/metrics/plugin.go b/plugins/metrics/plugin.go
deleted file mode 100644
index d285e609..00000000
--- a/plugins/metrics/plugin.go
+++ /dev/null
@@ -1,242 +0,0 @@
-package metrics
-
-import (
- "context"
- "crypto/tls"
- "net/http"
- "sync"
- "time"
-
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/collectors"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "golang.org/x/sys/cpu"
-)
-
-const (
- // PluginName declares plugin name.
- PluginName = "metrics"
- // maxHeaderSize declares max header size for prometheus server
- maxHeaderSize = 1024 * 1024 * 100 // 104MB
-)
-
-// Plugin to manage application metrics using Prometheus.
-type Plugin struct {
- cfg *Config
- log logger.Logger
- mu sync.Mutex // all receivers are pointers
- http *http.Server
- collectors sync.Map // all receivers are pointers
- registry *prometheus.Registry
-
- // prometheus Collectors
- statProviders []StatProvider
-}
-
-// Init service.
-func (p *Plugin) Init(cfg config.Configurer, log logger.Logger) error {
- const op = errors.Op("metrics_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(PluginName, &p.cfg)
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
-
- p.cfg.InitDefaults()
-
- p.log = log
- p.registry = prometheus.NewRegistry()
-
- // Default
- err = p.registry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
- if err != nil {
- return errors.E(op, err)
- }
-
- // Default
- err = p.registry.Register(collectors.NewGoCollector())
- if err != nil {
- return errors.E(op, err)
- }
-
- cl, err := p.cfg.getCollectors()
- if err != nil {
- return errors.E(op, err)
- }
-
- // Register invocation will be later in the Serve method
- for k, v := range cl {
- p.collectors.Store(k, v)
- }
-
- p.statProviders = make([]StatProvider, 0, 2)
-
- return nil
-}
-
-// Register new prometheus collector.
-func (p *Plugin) Register(c prometheus.Collector) error {
- return p.registry.Register(c)
-}
-
-// Serve prometheus metrics service.
-func (p *Plugin) Serve() chan error {
- errCh := make(chan error, 1)
-
- // register Collected stat providers
- for i := 0; i < len(p.statProviders); i++ {
- sp := p.statProviders[i]
- for _, c := range sp.MetricsCollector() {
- err := p.registry.Register(c)
- if err != nil {
- errCh <- err
- return errCh
- }
- }
- }
-
- p.collectors.Range(func(key, value interface{}) bool {
- // key - name
- // value - prometheus.Collector
- c := value.(prometheus.Collector)
- if err := p.registry.Register(c); err != nil {
- errCh <- err
- return false
- }
-
- return true
- })
-
- var topCipherSuites []uint16
- var defaultCipherSuitesTLS13 []uint16
-
- hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
- hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
- // Keep in sync with crypto/aes/cipher_s390x.go.
- hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
-
- hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
-
- if hasGCMAsm {
- // If AES-GCM hardware is provided then prioritize AES-GCM
- // cipher suites.
- topCipherSuites = []uint16{
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- }
- defaultCipherSuitesTLS13 = []uint16{
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_AES_256_GCM_SHA384,
- }
- } else {
- // Without AES-GCM hardware, we put the ChaCha20-Poly1305
- // cipher suites first.
- topCipherSuites = []uint16{
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- }
- defaultCipherSuitesTLS13 = []uint16{
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_AES_256_GCM_SHA384,
- }
- }
-
- DefaultCipherSuites := make([]uint16, 0, 22)
- DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...)
- DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...)
-
- p.http = &http.Server{
- Addr: p.cfg.Address,
- Handler: promhttp.HandlerFor(p.registry, promhttp.HandlerOpts{}),
- IdleTimeout: time.Hour * 24,
- ReadTimeout: time.Minute * 60,
- MaxHeaderBytes: maxHeaderSize,
- ReadHeaderTimeout: time.Minute * 60,
- WriteTimeout: time.Minute * 60,
- TLSConfig: &tls.Config{
- CurvePreferences: []tls.CurveID{
- tls.CurveP256,
- tls.CurveP384,
- tls.CurveP521,
- tls.X25519,
- },
- CipherSuites: DefaultCipherSuites,
- MinVersion: tls.VersionTLS12,
- PreferServerCipherSuites: true,
- },
- }
-
- go func() {
- err := p.http.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- errCh <- err
- return
- }
- }()
-
- return errCh
-}
-
-// Stop prometheus metrics service.
-func (p *Plugin) Stop() error {
- p.mu.Lock()
- defer p.mu.Unlock()
-
- if p.http != nil {
- // timeout is 10 seconds
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
- defer cancel()
- err := p.http.Shutdown(ctx)
- if err != nil {
- // Function should be Stop() error
- p.log.Error("stop error", "error", errors.Errorf("error shutting down the metrics server: error %v", err))
- }
- }
- return nil
-}
-
-// Collects used to collect all plugins which implement metrics.StatProvider interface (and Named)
-func (p *Plugin) Collects() []interface{} {
- return []interface{}{
- p.AddStatProvider,
- }
-}
-
-// AddStatProvider adds a metrics provider
-func (p *Plugin) AddStatProvider(stat StatProvider) error {
- p.statProviders = append(p.statProviders, stat)
-
- return nil
-}
-
-// Name returns user friendly plugin name
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-// RPC interface satisfaction
-func (p *Plugin) RPC() interface{} {
- return &rpcServer{
- svc: p,
- log: p.log,
- }
-}
-
-// Available interface implementation
-func (p *Plugin) Available() {}
diff --git a/plugins/metrics/rpc.go b/plugins/metrics/rpc.go
deleted file mode 100644
index 538cdb78..00000000
--- a/plugins/metrics/rpc.go
+++ /dev/null
@@ -1,294 +0,0 @@
-package metrics
-
-import (
- "github.com/prometheus/client_golang/prometheus"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-type rpcServer struct {
- svc *Plugin
- log logger.Logger
-}
-
-// Metric represent single metric produced by the application.
-type Metric struct {
- // Collector name.
- Name string
-
- // Collector value.
- Value float64
-
- // Labels associated with metric. Only for vector metrics. Must be provided in a form of label values.
- Labels []string
-}
-
-// Add new metric to the designated collector.
-func (rpc *rpcServer) Add(m *Metric, ok *bool) error {
- const op = errors.Op("metrics_plugin_add")
- rpc.log.Info("adding metric", "name", m.Name, "value", m.Value, "labels", m.Labels)
- c, exist := rpc.svc.collectors.Load(m.Name)
- if !exist {
- rpc.log.Error("undefined collector", "collector", m.Name)
- return errors.E(op, errors.Errorf("undefined collector %s, try first Declare the desired collector", m.Name))
- }
-
- switch c := c.(type) {
- case prometheus.Gauge:
- c.Add(m.Value)
-
- case *prometheus.GaugeVec:
- if len(m.Labels) == 0 {
- rpc.log.Error("required labels for collector", "collector", m.Name)
- return errors.E(op, errors.Errorf("required labels for collector %s", m.Name))
- }
-
- gauge, err := c.GetMetricWithLabelValues(m.Labels...)
- if err != nil {
- rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels)
- return errors.E(op, err)
- }
- gauge.Add(m.Value)
- case prometheus.Counter:
- c.Add(m.Value)
-
- case *prometheus.CounterVec:
- if len(m.Labels) == 0 {
- return errors.E(op, errors.Errorf("required labels for collector `%s`", m.Name))
- }
-
- gauge, err := c.GetMetricWithLabelValues(m.Labels...)
- if err != nil {
- rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels)
- return errors.E(op, err)
- }
- gauge.Add(m.Value)
-
- default:
- return errors.E(op, errors.Errorf("collector %s does not support method `Add`", m.Name))
- }
-
- // RPC, set ok to true as return value. Need by rpc.Call reply argument
- *ok = true
- rpc.log.Info("metric successfully added", "name", m.Name, "labels", m.Labels, "value", m.Value)
- return nil
-}
-
-// Sub subtract the value from the specific metric (gauge only).
-func (rpc *rpcServer) Sub(m *Metric, ok *bool) error {
- const op = errors.Op("metrics_plugin_sub")
- rpc.log.Info("subtracting value from metric", "name", m.Name, "value", m.Value, "labels", m.Labels)
- c, exist := rpc.svc.collectors.Load(m.Name)
- if !exist {
- rpc.log.Error("undefined collector", "name", m.Name, "value", m.Value, "labels", m.Labels)
- return errors.E(op, errors.Errorf("undefined collector %s", m.Name))
- }
- if c == nil {
- // can it be nil ??? I guess can't
- return errors.E(op, errors.Errorf("undefined collector %s", m.Name))
- }
-
- switch c := c.(type) {
- case prometheus.Gauge:
- c.Sub(m.Value)
-
- case *prometheus.GaugeVec:
- if len(m.Labels) == 0 {
- rpc.log.Error("required labels for collector, but none was provided", "name", m.Name, "value", m.Value)
- return errors.E(op, errors.Errorf("required labels for collector %s", m.Name))
- }
-
- gauge, err := c.GetMetricWithLabelValues(m.Labels...)
- if err != nil {
- rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels)
- return errors.E(op, err)
- }
- gauge.Sub(m.Value)
- default:
- return errors.E(op, errors.Errorf("collector `%s` does not support method `Sub`", m.Name))
- }
- rpc.log.Info("subtracting operation finished successfully", "name", m.Name, "labels", m.Labels, "value", m.Value)
-
- *ok = true
- return nil
-}
-
-// Observe the value (histogram and summary only).
-func (rpc *rpcServer) Observe(m *Metric, ok *bool) error {
- const op = errors.Op("metrics_plugin_observe")
- rpc.log.Info("observing metric", "name", m.Name, "value", m.Value, "labels", m.Labels)
-
- c, exist := rpc.svc.collectors.Load(m.Name)
- if !exist {
- rpc.log.Error("undefined collector", "name", m.Name, "value", m.Value, "labels", m.Labels)
- return errors.E(op, errors.Errorf("undefined collector %s", m.Name))
- }
- if c == nil {
- return errors.E(op, errors.Errorf("undefined collector %s", m.Name))
- }
-
- switch c := c.(type) {
- case *prometheus.SummaryVec:
- if len(m.Labels) == 0 {
- return errors.E(op, errors.Errorf("required labels for collector `%s`", m.Name))
- }
-
- observer, err := c.GetMetricWithLabelValues(m.Labels...)
- if err != nil {
- return errors.E(op, err)
- }
- observer.Observe(m.Value)
-
- case prometheus.Histogram:
- c.Observe(m.Value)
-
- case *prometheus.HistogramVec:
- if len(m.Labels) == 0 {
- return errors.E(op, errors.Errorf("required labels for collector `%s`", m.Name))
- }
-
- observer, err := c.GetMetricWithLabelValues(m.Labels...)
- if err != nil {
- rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels)
- return errors.E(op, err)
- }
- observer.Observe(m.Value)
- default:
- return errors.E(op, errors.Errorf("collector `%s` does not support method `Observe`", m.Name))
- }
-
- rpc.log.Info("observe operation finished successfully", "name", m.Name, "labels", m.Labels, "value", m.Value)
-
- *ok = true
- return nil
-}
-
-// Declare is used to register new collector in prometheus
-// THE TYPES ARE:
-// NamedCollector -> Collector with the name
-// bool -> RPC reply value
-// RETURNS:
-// error
-func (rpc *rpcServer) Declare(nc *NamedCollector, ok *bool) error {
- const op = errors.Op("metrics_plugin_declare")
- rpc.log.Info("declaring new metric", "name", nc.Name, "type", nc.Type, "namespace", nc.Namespace)
- _, exist := rpc.svc.collectors.Load(nc.Name)
- if exist {
- rpc.log.Error("metric with provided name already exist", "name", nc.Name, "type", nc.Type, "namespace", nc.Namespace)
- return errors.E(op, errors.Errorf("tried to register existing collector with the name `%s`", nc.Name))
- }
-
- var collector prometheus.Collector
- switch nc.Type {
- case Histogram:
- opts := prometheus.HistogramOpts{
- Name: nc.Name,
- Namespace: nc.Namespace,
- Subsystem: nc.Subsystem,
- Help: nc.Help,
- Buckets: nc.Buckets,
- }
-
- if len(nc.Labels) != 0 {
- collector = prometheus.NewHistogramVec(opts, nc.Labels)
- } else {
- collector = prometheus.NewHistogram(opts)
- }
- case Gauge:
- opts := prometheus.GaugeOpts{
- Name: nc.Name,
- Namespace: nc.Namespace,
- Subsystem: nc.Subsystem,
- Help: nc.Help,
- }
-
- if len(nc.Labels) != 0 {
- collector = prometheus.NewGaugeVec(opts, nc.Labels)
- } else {
- collector = prometheus.NewGauge(opts)
- }
- case Counter:
- opts := prometheus.CounterOpts{
- Name: nc.Name,
- Namespace: nc.Namespace,
- Subsystem: nc.Subsystem,
- Help: nc.Help,
- }
-
- if len(nc.Labels) != 0 {
- collector = prometheus.NewCounterVec(opts, nc.Labels)
- } else {
- collector = prometheus.NewCounter(opts)
- }
- case Summary:
- opts := prometheus.SummaryOpts{
- Name: nc.Name,
- Namespace: nc.Namespace,
- Subsystem: nc.Subsystem,
- Help: nc.Help,
- }
-
- if len(nc.Labels) != 0 {
- collector = prometheus.NewSummaryVec(opts, nc.Labels)
- } else {
- collector = prometheus.NewSummary(opts)
- }
-
- default:
- return errors.E(op, errors.Errorf("unknown collector type %s", nc.Type))
- }
-
- // add collector to sync.Map
- rpc.svc.collectors.Store(nc.Name, collector)
- // that method might panic, we handle it by recover
- err := rpc.svc.Register(collector)
- if err != nil {
- *ok = false
- return errors.E(op, err)
- }
-
- rpc.log.Info("metric successfully added", "name", nc.Name, "type", nc.Type, "namespace", nc.Namespace)
-
- *ok = true
- return nil
-}
-
-// Set the metric value (only for gaude).
-func (rpc *rpcServer) Set(m *Metric, ok *bool) (err error) {
- const op = errors.Op("metrics_plugin_set")
- rpc.log.Info("observing metric", "name", m.Name, "value", m.Value, "labels", m.Labels)
-
- c, exist := rpc.svc.collectors.Load(m.Name)
- if !exist {
- return errors.E(op, errors.Errorf("undefined collector %s", m.Name))
- }
- if c == nil {
- return errors.E(op, errors.Errorf("undefined collector %s", m.Name))
- }
-
- switch c := c.(type) {
- case prometheus.Gauge:
- c.Set(m.Value)
-
- case *prometheus.GaugeVec:
- if len(m.Labels) == 0 {
- rpc.log.Error("required labels for collector", "collector", m.Name)
- return errors.E(op, errors.Errorf("required labels for collector %s", m.Name))
- }
-
- gauge, err := c.GetMetricWithLabelValues(m.Labels...)
- if err != nil {
- rpc.log.Error("failed to get metrics with label values", "collector", m.Name, "labels", m.Labels)
- return errors.E(op, err)
- }
- gauge.Set(m.Value)
-
- default:
- return errors.E(op, errors.Errorf("collector `%s` does not support method Set", m.Name))
- }
-
- rpc.log.Info("set operation finished successfully", "name", m.Name, "labels", m.Labels, "value", m.Value)
-
- *ok = true
- return nil
-}
diff --git a/plugins/redis/config.go b/plugins/redis/config.go
deleted file mode 100644
index 9acb4b47..00000000
--- a/plugins/redis/config.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package redis
-
-import "time"
-
-type Config struct {
- Addrs []string `mapstructure:"addrs"`
- DB int `mapstructure:"db"`
- Username string `mapstructure:"username"`
- Password string `mapstructure:"password"`
- MasterName string `mapstructure:"master_name"`
- SentinelPassword string `mapstructure:"sentinel_password"`
- RouteByLatency bool `mapstructure:"route_by_latency"`
- RouteRandomly bool `mapstructure:"route_randomly"`
- MaxRetries int `mapstructure:"max_retries"`
- DialTimeout time.Duration `mapstructure:"dial_timeout"`
- MinRetryBackoff time.Duration `mapstructure:"min_retry_backoff"`
- MaxRetryBackoff time.Duration `mapstructure:"max_retry_backoff"`
- PoolSize int `mapstructure:"pool_size"`
- MinIdleConns int `mapstructure:"min_idle_conns"`
- MaxConnAge time.Duration `mapstructure:"max_conn_age"`
- ReadTimeout time.Duration `mapstructure:"read_timeout"`
- WriteTimeout time.Duration `mapstructure:"write_timeout"`
- PoolTimeout time.Duration `mapstructure:"pool_timeout"`
- IdleTimeout time.Duration `mapstructure:"idle_timeout"`
- IdleCheckFreq time.Duration `mapstructure:"idle_check_freq"`
- ReadOnly bool `mapstructure:"read_only"`
-}
-
-// InitDefaults initializing fill config with default values
-func (s *Config) InitDefaults() {
- if s.Addrs == nil {
- s.Addrs = []string{"127.0.0.1:6379"} // default addr is pointing to local storage
- }
-}
diff --git a/plugins/redis/kv/config.go b/plugins/redis/kv/config.go
deleted file mode 100644
index 5bd772a9..00000000
--- a/plugins/redis/kv/config.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package kv
-
-import (
- "time"
-)
-
-type Config struct {
- Addrs []string `mapstructure:"addrs"`
- DB int `mapstructure:"db"`
- Username string `mapstructure:"username"`
- Password string `mapstructure:"password"`
- MasterName string `mapstructure:"master_name"`
- SentinelPassword string `mapstructure:"sentinel_password"`
- RouteByLatency bool `mapstructure:"route_by_latency"`
- RouteRandomly bool `mapstructure:"route_randomly"`
- MaxRetries int `mapstructure:"max_retries"`
- DialTimeout time.Duration `mapstructure:"dial_timeout"`
- MinRetryBackoff time.Duration `mapstructure:"min_retry_backoff"`
- MaxRetryBackoff time.Duration `mapstructure:"max_retry_backoff"`
- PoolSize int `mapstructure:"pool_size"`
- MinIdleConns int `mapstructure:"min_idle_conns"`
- MaxConnAge time.Duration `mapstructure:"max_conn_age"`
- ReadTimeout time.Duration `mapstructure:"read_timeout"`
- WriteTimeout time.Duration `mapstructure:"write_timeout"`
- PoolTimeout time.Duration `mapstructure:"pool_timeout"`
- IdleTimeout time.Duration `mapstructure:"idle_timeout"`
- IdleCheckFreq time.Duration `mapstructure:"idle_check_freq"`
- ReadOnly bool `mapstructure:"read_only"`
-}
-
-// InitDefaults initializing fill config with default values
-func (s *Config) InitDefaults() {
- if s.Addrs == nil {
- s.Addrs = []string{"127.0.0.1:6379"} // default addr is pointing to local storage
- }
-}
diff --git a/plugins/redis/kv/kv.go b/plugins/redis/kv/kv.go
deleted file mode 100644
index ae55d332..00000000
--- a/plugins/redis/kv/kv.go
+++ /dev/null
@@ -1,255 +0,0 @@
-package kv
-
-import (
- "context"
- "strings"
- "time"
-
- "github.com/go-redis/redis/v8"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- kvv1 "github.com/spiral/roadrunner/v2/proto/kv/v1beta"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-type Driver struct {
- universalClient redis.UniversalClient
- log logger.Logger
- cfg *Config
-}
-
-func NewRedisDriver(log logger.Logger, key string, cfgPlugin config.Configurer) (*Driver, error) {
- const op = errors.Op("new_redis_driver")
-
- d := &Driver{
- log: log,
- }
-
- // will be different for every connected driver
- err := cfgPlugin.UnmarshalKey(key, &d.cfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- if d.cfg == nil {
- return nil, errors.E(op, errors.Errorf("config not found by provided key: %s", key))
- }
-
- d.cfg.InitDefaults()
-
- d.universalClient = redis.NewUniversalClient(&redis.UniversalOptions{
- Addrs: d.cfg.Addrs,
- DB: d.cfg.DB,
- Username: d.cfg.Username,
- Password: d.cfg.Password,
- SentinelPassword: d.cfg.SentinelPassword,
- MaxRetries: d.cfg.MaxRetries,
- MinRetryBackoff: d.cfg.MaxRetryBackoff,
- MaxRetryBackoff: d.cfg.MaxRetryBackoff,
- DialTimeout: d.cfg.DialTimeout,
- ReadTimeout: d.cfg.ReadTimeout,
- WriteTimeout: d.cfg.WriteTimeout,
- PoolSize: d.cfg.PoolSize,
- MinIdleConns: d.cfg.MinIdleConns,
- MaxConnAge: d.cfg.MaxConnAge,
- PoolTimeout: d.cfg.PoolTimeout,
- IdleTimeout: d.cfg.IdleTimeout,
- IdleCheckFrequency: d.cfg.IdleCheckFreq,
- ReadOnly: d.cfg.ReadOnly,
- RouteByLatency: d.cfg.RouteByLatency,
- RouteRandomly: d.cfg.RouteRandomly,
- MasterName: d.cfg.MasterName,
- })
-
- return d, nil
-}
-
-// Has checks if value exists.
-func (d *Driver) Has(keys ...string) (map[string]bool, error) {
- const op = errors.Op("redis_driver_has")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- m := make(map[string]bool, len(keys))
- for _, key := range keys {
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
-
- exist, err := d.universalClient.Exists(context.Background(), key).Result()
- if err != nil {
- return nil, err
- }
- if exist == 1 {
- m[key] = true
- }
- }
- return m, nil
-}
-
-// Get loads key content into slice.
-func (d *Driver) Get(key string) ([]byte, error) {
- const op = errors.Op("redis_driver_get")
- // to get cases like " "
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- return d.universalClient.Get(context.Background(), key).Bytes()
-}
-
-// MGet loads content of multiple values (some values might be skipped).
-// https://redis.io/commands/mget
-// Returns slice with the interfaces with values
-func (d *Driver) MGet(keys ...string) (map[string][]byte, error) {
- const op = errors.Op("redis_driver_mget")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for _, key := range keys {
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- }
-
- m := make(map[string][]byte, len(keys))
-
- for _, k := range keys {
- cmd := d.universalClient.Get(context.Background(), k)
- if cmd.Err() != nil {
- if cmd.Err() == redis.Nil {
- continue
- }
- return nil, errors.E(op, cmd.Err())
- }
-
- m[k] = utils.AsBytes(cmd.Val())
- }
-
- return m, nil
-}
-
-// Set sets value with the TTL in seconds
-// https://redis.io/commands/set
-// Redis `SET key value [expiration]` command.
-//
-// Use expiration for `SETEX`-like behavior.
-// Zero expiration means the key has no expiration time.
-func (d *Driver) Set(items ...*kvv1.Item) error {
- const op = errors.Op("redis_driver_set")
- if items == nil {
- return errors.E(op, errors.NoKeys)
- }
- now := time.Now()
- for _, item := range items {
- if item == nil {
- return errors.E(op, errors.EmptyKey)
- }
-
- if item.Timeout == "" {
- err := d.universalClient.Set(context.Background(), item.Key, item.Value, 0).Err()
- if err != nil {
- return err
- }
- } else {
- t, err := time.Parse(time.RFC3339, item.Timeout)
- if err != nil {
- return err
- }
- err = d.universalClient.Set(context.Background(), item.Key, item.Value, t.Sub(now)).Err()
- if err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// Delete one or multiple keys.
-func (d *Driver) Delete(keys ...string) error {
- const op = errors.Op("redis_driver_delete")
- if keys == nil {
- return errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for _, key := range keys {
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return errors.E(op, errors.EmptyKey)
- }
- }
- return d.universalClient.Del(context.Background(), keys...).Err()
-}
-
-// MExpire https://redis.io/commands/expire
-// timeout in RFC3339
-func (d *Driver) MExpire(items ...*kvv1.Item) error {
- const op = errors.Op("redis_driver_mexpire")
- now := time.Now()
- for _, item := range items {
- if item == nil {
- continue
- }
- if item.Timeout == "" || strings.TrimSpace(item.Key) == "" {
- return errors.E(op, errors.Str("should set timeout and at least one key"))
- }
-
- t, err := time.Parse(time.RFC3339, item.Timeout)
- if err != nil {
- return err
- }
-
- // t guessed to be in future
- // for Redis we use t.Sub, it will result in seconds, like 4.2s
- d.universalClient.Expire(context.Background(), item.Key, t.Sub(now))
- }
-
- return nil
-}
-
-// TTL https://redis.io/commands/ttl
-// return time in seconds (float64) for a given keys
-func (d *Driver) TTL(keys ...string) (map[string]string, error) {
- const op = errors.Op("redis_driver_ttl")
- if keys == nil {
- return nil, errors.E(op, errors.NoKeys)
- }
-
- // should not be empty keys
- for _, key := range keys {
- keyTrimmed := strings.TrimSpace(key)
- if keyTrimmed == "" {
- return nil, errors.E(op, errors.EmptyKey)
- }
- }
-
- m := make(map[string]string, len(keys))
-
- for _, key := range keys {
- duration, err := d.universalClient.TTL(context.Background(), key).Result()
- if err != nil {
- return nil, err
- }
-
- m[key] = duration.String()
- }
- return m, nil
-}
-
-func (d *Driver) Clear() error {
- fdb := d.universalClient.FlushDB(context.Background())
- if fdb.Err() != nil {
- return fdb.Err()
- }
-
- return nil
-}
-
-func (d *Driver) Stop() {}
diff --git a/plugins/redis/plugin.go b/plugins/redis/plugin.go
deleted file mode 100644
index 961182a9..00000000
--- a/plugins/redis/plugin.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package redis
-
-import (
- "sync"
-
- "github.com/go-redis/redis/v8"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/kv"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- redis_kv "github.com/spiral/roadrunner/v2/plugins/redis/kv"
- redis_pubsub "github.com/spiral/roadrunner/v2/plugins/redis/pubsub"
-)
-
-const PluginName = "redis"
-
-type Plugin struct {
- sync.RWMutex
- // config for RR integration
- cfgPlugin config.Configurer
- // logger
- log logger.Logger
- // redis universal client
- universalClient redis.UniversalClient
-
- // fanIn implementation used to deliver messages from all channels to the single websocket point
- stopCh chan struct{}
-}
-
-func (p *Plugin) Init(cfg config.Configurer, log logger.Logger) error {
- p.log = log
- p.cfgPlugin = cfg
- p.stopCh = make(chan struct{}, 1)
-
- return nil
-}
-
-func (p *Plugin) Serve() chan error {
- return make(chan error)
-}
-
-func (p *Plugin) Stop() error {
- const op = errors.Op("redis_plugin_stop")
- p.stopCh <- struct{}{}
-
- if p.universalClient != nil {
- err := p.universalClient.Close()
- if err != nil {
- return errors.E(op, err)
- }
- }
-
- return nil
-}
-
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (p *Plugin) Available() {}
-
-// KVConstruct provides KV storage implementation over the redis plugin
-func (p *Plugin) KVConstruct(key string) (kv.Storage, error) {
- const op = errors.Op("redis_plugin_provide")
- st, err := redis_kv.NewRedisDriver(p.log, key, p.cfgPlugin)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return st, nil
-}
-
-func (p *Plugin) PSConstruct(key string) (pubsub.PubSub, error) {
- return redis_pubsub.NewPubSubDriver(p.log, key, p.cfgPlugin, p.stopCh)
-}
diff --git a/plugins/redis/pubsub/channel.go b/plugins/redis/pubsub/channel.go
deleted file mode 100644
index a1655ab2..00000000
--- a/plugins/redis/pubsub/channel.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package pubsub
-
-import (
- "context"
- "sync"
-
- "github.com/go-redis/redis/v8"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-type redisChannel struct {
- sync.Mutex
-
- // redis client
- client redis.UniversalClient
- pubsub *redis.PubSub
-
- log logger.Logger
-
- // out channel with all subs
- out chan *pubsub.Message
-
- exit chan struct{}
-}
-
-func newRedisChannel(redisClient redis.UniversalClient, log logger.Logger) *redisChannel {
- out := make(chan *pubsub.Message, 100)
- fi := &redisChannel{
- out: out,
- client: redisClient,
- pubsub: redisClient.Subscribe(context.Background()),
- exit: make(chan struct{}),
- log: log,
- }
-
- // start reading messages
- go fi.read()
-
- return fi
-}
-
-func (r *redisChannel) sub(topics ...string) error {
- const op = errors.Op("redis_sub")
- err := r.pubsub.Subscribe(context.Background(), topics...)
- if err != nil {
- return errors.E(op, err)
- }
- return nil
-}
-
-// read reads messages from the pubsub subscription
-func (r *redisChannel) read() {
- for {
- select {
- // here we receive message from us (which we sent before in Publish)
- // it should be compatible with the pubsub.Message structure
- // payload should be in the redis.message.payload field
-
- case msg, ok := <-r.pubsub.Channel():
- // channel closed
- if !ok {
- return
- }
-
- r.out <- &pubsub.Message{
- Topic: msg.Channel,
- Payload: utils.AsBytes(msg.Payload),
- }
-
- case <-r.exit:
- return
- }
- }
-}
-
-func (r *redisChannel) unsub(topic string) error {
- const op = errors.Op("redis_unsub")
- err := r.pubsub.Unsubscribe(context.Background(), topic)
- if err != nil {
- return errors.E(op, err)
- }
- return nil
-}
-
-func (r *redisChannel) stop() error {
- r.exit <- struct{}{}
- close(r.out)
- close(r.exit)
- return nil
-}
-
-func (r *redisChannel) message() chan *pubsub.Message {
- return r.out
-}
diff --git a/plugins/redis/pubsub/config.go b/plugins/redis/pubsub/config.go
deleted file mode 100644
index bf8d2fc9..00000000
--- a/plugins/redis/pubsub/config.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package pubsub
-
-import "time"
-
-type Config struct {
- Addrs []string `mapstructure:"addrs"`
- DB int `mapstructure:"db"`
- Username string `mapstructure:"username"`
- Password string `mapstructure:"password"`
- MasterName string `mapstructure:"master_name"`
- SentinelPassword string `mapstructure:"sentinel_password"`
- RouteByLatency bool `mapstructure:"route_by_latency"`
- RouteRandomly bool `mapstructure:"route_randomly"`
- MaxRetries int `mapstructure:"max_retries"`
- DialTimeout time.Duration `mapstructure:"dial_timeout"`
- MinRetryBackoff time.Duration `mapstructure:"min_retry_backoff"`
- MaxRetryBackoff time.Duration `mapstructure:"max_retry_backoff"`
- PoolSize int `mapstructure:"pool_size"`
- MinIdleConns int `mapstructure:"min_idle_conns"`
- MaxConnAge time.Duration `mapstructure:"max_conn_age"`
- ReadTimeout time.Duration `mapstructure:"read_timeout"`
- WriteTimeout time.Duration `mapstructure:"write_timeout"`
- PoolTimeout time.Duration `mapstructure:"pool_timeout"`
- IdleTimeout time.Duration `mapstructure:"idle_timeout"`
- IdleCheckFreq time.Duration `mapstructure:"idle_check_freq"`
- ReadOnly bool `mapstructure:"read_only"`
-}
-
-// InitDefaults initializing fill config with default values
-func (s *Config) InitDefaults() {
- if s.Addrs == nil {
- s.Addrs = []string{"127.0.0.1:6379"} // default addr is pointing to local storage
- }
-}
diff --git a/plugins/redis/pubsub/pubsub.go b/plugins/redis/pubsub/pubsub.go
deleted file mode 100644
index 3561ef18..00000000
--- a/plugins/redis/pubsub/pubsub.go
+++ /dev/null
@@ -1,187 +0,0 @@
-package pubsub
-
-import (
- "context"
- "sync"
-
- "github.com/go-redis/redis/v8"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-type PubSubDriver struct {
- sync.RWMutex
- cfg *Config
-
- log logger.Logger
- channel *redisChannel
- universalClient redis.UniversalClient
- stopCh chan struct{}
-}
-
-func NewPubSubDriver(log logger.Logger, key string, cfgPlugin config.Configurer, stopCh chan struct{}) (*PubSubDriver, error) {
- const op = errors.Op("new_pub_sub_driver")
- ps := &PubSubDriver{
- log: log,
- stopCh: stopCh,
- }
-
- // will be different for every connected driver
- err := cfgPlugin.UnmarshalKey(key, &ps.cfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- if ps.cfg == nil {
- return nil, errors.E(op, errors.Errorf("config not found by provided key: %s", key))
- }
-
- ps.cfg.InitDefaults()
-
- ps.universalClient = redis.NewUniversalClient(&redis.UniversalOptions{
- Addrs: ps.cfg.Addrs,
- DB: ps.cfg.DB,
- Username: ps.cfg.Username,
- Password: ps.cfg.Password,
- SentinelPassword: ps.cfg.SentinelPassword,
- MaxRetries: ps.cfg.MaxRetries,
- MinRetryBackoff: ps.cfg.MaxRetryBackoff,
- MaxRetryBackoff: ps.cfg.MaxRetryBackoff,
- DialTimeout: ps.cfg.DialTimeout,
- ReadTimeout: ps.cfg.ReadTimeout,
- WriteTimeout: ps.cfg.WriteTimeout,
- PoolSize: ps.cfg.PoolSize,
- MinIdleConns: ps.cfg.MinIdleConns,
- MaxConnAge: ps.cfg.MaxConnAge,
- PoolTimeout: ps.cfg.PoolTimeout,
- IdleTimeout: ps.cfg.IdleTimeout,
- IdleCheckFrequency: ps.cfg.IdleCheckFreq,
- ReadOnly: ps.cfg.ReadOnly,
- RouteByLatency: ps.cfg.RouteByLatency,
- RouteRandomly: ps.cfg.RouteRandomly,
- MasterName: ps.cfg.MasterName,
- })
-
- statusCmd := ps.universalClient.Ping(context.Background())
- if statusCmd.Err() != nil {
- return nil, statusCmd.Err()
- }
-
- ps.channel = newRedisChannel(ps.universalClient, log)
-
- ps.stop()
-
- return ps, nil
-}
-
-func (p *PubSubDriver) stop() {
- go func() {
- for range p.stopCh {
- _ = p.channel.stop()
- return
- }
- }()
-}
-
-func (p *PubSubDriver) Publish(msg *pubsub.Message) error {
- p.Lock()
- defer p.Unlock()
-
- f := p.universalClient.Publish(context.Background(), msg.Topic, msg.Payload)
- if f.Err() != nil {
- return f.Err()
- }
-
- return nil
-}
-
-func (p *PubSubDriver) PublishAsync(msg *pubsub.Message) {
- go func() {
- p.Lock()
- defer p.Unlock()
-
- f := p.universalClient.Publish(context.Background(), msg.Topic, msg.Payload)
- if f.Err() != nil {
- p.log.Error("redis publish", "error", f.Err())
- }
- }()
-}
-
-func (p *PubSubDriver) Subscribe(connectionID string, topics ...string) error {
- // just add a connection
- for i := 0; i < len(topics); i++ {
- // key - topic
- // value - connectionID
- hset := p.universalClient.SAdd(context.Background(), topics[i], connectionID)
- res, err := hset.Result()
- if err != nil {
- return err
- }
- if res == 0 {
- p.log.Warn("could not subscribe to the provided topic, you might be already subscribed to it", "connectionID", connectionID, "topic", topics[i])
- continue
- }
- }
-
- // and subscribe after
- return p.channel.sub(topics...)
-}
-
-func (p *PubSubDriver) Unsubscribe(connectionID string, topics ...string) error {
- // Remove topics from the storage
- for i := 0; i < len(topics); i++ {
- srem := p.universalClient.SRem(context.Background(), topics[i], connectionID)
- if srem.Err() != nil {
- return srem.Err()
- }
- }
-
- for i := 0; i < len(topics); i++ {
- // if there are no such topics, we can safely unsubscribe from the redis
- exists := p.universalClient.Exists(context.Background(), topics[i])
- res, err := exists.Result()
- if err != nil {
- return err
- }
-
- // if we have associated connections - skip
- if res == 1 { // exists means that topic still exists and some other nodes may have connections associated with it
- continue
- }
-
- // else - unsubscribe
- err = p.channel.unsub(topics[i])
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (p *PubSubDriver) Connections(topic string, res map[string]struct{}) {
- hget := p.universalClient.SMembersMap(context.Background(), topic)
- r, err := hget.Result()
- if err != nil {
- panic(err)
- }
-
- // assign connections
- // res expected to be from the sync.Pool
- for k := range r {
- res[k] = struct{}{}
- }
-}
-
-// Next return next message
-func (p *PubSubDriver) Next(ctx context.Context) (*pubsub.Message, error) {
- const op = errors.Op("redis_driver_next")
- select {
- case msg := <-p.channel.message():
- return msg, nil
- case <-ctx.Done():
- return nil, errors.E(op, errors.TimeOut, ctx.Err())
- }
-}
diff --git a/plugins/reload/config.go b/plugins/reload/config.go
deleted file mode 100644
index 6fd3af70..00000000
--- a/plugins/reload/config.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package reload
-
-import (
- "time"
-
- "github.com/spiral/errors"
-)
-
-// Config is a Reload configuration point.
-type Config struct {
- // Interval is a global refresh interval
- Interval time.Duration
-
- // Patterns is a global file patterns to watch. It will be applied to every directory in project
- Patterns []string
-
- // Services is set of services which would be reloaded in case of FS changes
- Services map[string]ServiceConfig
-}
-
-type ServiceConfig struct {
- // Enabled indicates that service must be watched, doest not required when any other option specified
- Enabled bool
-
- // Recursive is options to use nested files from root folder
- Recursive bool
-
- // Patterns is per-service specific files to watch
- Patterns []string
-
- // Dirs is per-service specific dirs which will be combined with Patterns
- Dirs []string
-
- // Ignore is set of files which would not be watched
- Ignore []string
-}
-
-// InitDefaults sets missing values to their default values.
-func (c *Config) InitDefaults() {
- if c.Interval == 0 {
- c.Interval = time.Second
- }
- if c.Patterns == nil {
- c.Patterns = []string{".php"}
- }
-}
-
-// Valid validates the configuration.
-func (c *Config) Valid() error {
- const op = errors.Op("reload_plugin_valid")
- if c.Interval < time.Second {
- return errors.E(op, errors.Str("too short interval"))
- }
-
- if c.Services == nil {
- return errors.E(op, errors.Str("should add at least 1 service"))
- } else if len(c.Services) == 0 {
- return errors.E(op, errors.Str("service initialized, however, no config added"))
- }
-
- return nil
-}
diff --git a/plugins/reload/plugin.go b/plugins/reload/plugin.go
deleted file mode 100644
index a9a5a63c..00000000
--- a/plugins/reload/plugin.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package reload
-
-import (
- "os"
- "strings"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/resetter"
-)
-
-// PluginName contains default plugin name.
-const PluginName string = "reload"
-const thresholdChanBuffer uint = 1000
-
-type Plugin struct {
- cfg *Config
- log logger.Logger
- watcher *Watcher
- services map[string]interface{}
- res *resetter.Plugin
- stopc chan struct{}
-}
-
-// Init controller service
-func (s *Plugin) Init(cfg config.Configurer, log logger.Logger, res *resetter.Plugin) error {
- const op = errors.Op("reload_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(PluginName, &s.cfg)
- if err != nil {
- // disable plugin in case of error
- return errors.E(op, errors.Disabled, err)
- }
-
- s.cfg.InitDefaults()
-
- s.log = log
- s.res = res
- s.stopc = make(chan struct{}, 1)
- s.services = make(map[string]interface{})
-
- configs := make([]WatcherConfig, 0, len(s.cfg.Services))
-
- for serviceName, serviceConfig := range s.cfg.Services {
- ignored, errIgn := ConvertIgnored(serviceConfig.Ignore)
- if errIgn != nil {
- return errors.E(op, err)
- }
- configs = append(configs, WatcherConfig{
- ServiceName: serviceName,
- Recursive: serviceConfig.Recursive,
- Directories: serviceConfig.Dirs,
- FilterHooks: func(filename string, patterns []string) error {
- for i := 0; i < len(patterns); i++ {
- if strings.Contains(filename, patterns[i]) {
- return nil
- }
- }
- return errors.E(op, errors.SkipFile)
- },
- Files: make(map[string]os.FileInfo),
- Ignored: ignored,
- FilePatterns: append(serviceConfig.Patterns, s.cfg.Patterns...),
- })
- }
-
- s.watcher, err = NewWatcher(configs, s.log)
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (s *Plugin) Serve() chan error {
- const op = errors.Op("reload_plugin_serve")
- errCh := make(chan error, 1)
- if s.cfg.Interval < time.Second {
- errCh <- errors.E(op, errors.Str("reload interval is too fast"))
- return errCh
- }
-
- // make a map with unique services
- // so, if we would have 100 events from http service
- // in map we would see only 1 key, and it's config
- thCh := make(chan struct {
- serviceConfig ServiceConfig
- service string
- }, thresholdChanBuffer)
-
- // use the same interval
- timer := time.NewTimer(s.cfg.Interval)
-
- go func() {
- for e := range s.watcher.Event {
- thCh <- struct {
- serviceConfig ServiceConfig
- service string
- }{serviceConfig: s.cfg.Services[e.service], service: e.service}
- }
- }()
-
- // map with config by services
- updated := make(map[string]ServiceConfig, len(s.cfg.Services))
-
- go func() {
- for {
- select {
- case cfg := <-thCh:
- // logic is following:
- // restart
- timer.Stop()
- // replace previous value in map by more recent without adding new one
- updated[cfg.service] = cfg.serviceConfig
- // if we are getting a lot of events, we shouldn't restart particular service on each of it (user doing batch move or very fast typing)
- // instead, we are resetting the timer and wait for s.cfg.Interval time
- // If there is no more events, we restart service only once
- timer.Reset(s.cfg.Interval)
- case <-timer.C:
- if len(updated) > 0 {
- for name := range updated {
- err := s.res.Reset(name)
- if err != nil {
- timer.Stop()
- errCh <- errors.E(op, err)
- return
- }
- }
- // zero map
- updated = make(map[string]ServiceConfig, len(s.cfg.Services))
- }
- case <-s.stopc:
- timer.Stop()
- return
- }
- }
- }()
-
- go func() {
- err := s.watcher.StartPolling(s.cfg.Interval)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
- }()
-
- return errCh
-}
-
-func (s *Plugin) Stop() error {
- s.watcher.Stop()
- s.stopc <- struct{}{}
- return nil
-}
-
-func (s *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (s *Plugin) Available() {
-}
diff --git a/plugins/reload/watcher.go b/plugins/reload/watcher.go
deleted file mode 100644
index c40c2fdf..00000000
--- a/plugins/reload/watcher.go
+++ /dev/null
@@ -1,372 +0,0 @@
-package reload
-
-import (
- "io/ioutil"
- "os"
- "path/filepath"
- "sync"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-// SimpleHook is used to filter by simple criteria, CONTAINS
-type SimpleHook func(filename string, pattern []string) error
-
-// An Event describes an event that is received when files or directory
-// changes occur. It includes the os.FileInfo of the changed file or
-// directory and the type of event that's occurred and the full path of the file.
-type Event struct {
- Path string
- Info os.FileInfo
-
- service string // type of service, http, grpc, etc...
-}
-
-type WatcherConfig struct {
- // service name
- ServiceName string
-
- // Recursive or just add by singe directory
- Recursive bool
-
- // Directories used per-service
- Directories []string
-
- // simple hook, just CONTAINS
- FilterHooks func(filename string, pattern []string) error
-
- // path to file with Files
- Files map[string]os.FileInfo
-
- // Ignored Directories, used map for O(1) amortized get
- Ignored map[string]struct{}
-
- // FilePatterns to ignore
- FilePatterns []string
-}
-
-type Watcher struct {
- // main event channel
- Event chan Event
- close chan struct{}
-
- // =============================
- mu *sync.Mutex
-
- // indicates is walker started or not
- started bool
-
- // config for each service
- // need pointer here to assign files
- watcherConfigs map[string]WatcherConfig
-
- // logger
- log logger.Logger
-}
-
-// Options is used to set Watcher Options
-type Options func(*Watcher)
-
-// NewWatcher returns new instance of File Watcher
-func NewWatcher(configs []WatcherConfig, log logger.Logger, options ...Options) (*Watcher, error) {
- w := &Watcher{
- Event: make(chan Event),
- mu: &sync.Mutex{},
-
- log: log,
-
- close: make(chan struct{}),
-
- //workingDir: workDir,
- watcherConfigs: make(map[string]WatcherConfig),
- }
-
- // add watcherConfigs by service names
- for _, v := range configs {
- w.watcherConfigs[v.ServiceName] = v
- }
-
- // apply options
- for _, option := range options {
- option(w)
- }
- err := w.initFs()
- if err != nil {
- return nil, err
- }
-
- return w, nil
-}
-
-// initFs makes initial map with files
-func (w *Watcher) initFs() error {
- const op = errors.Op("watcher_init_fs")
- for srvName, config := range w.watcherConfigs {
- fileList, err := w.retrieveFileList(srvName, config)
- if err != nil {
- return errors.E(op, err)
- }
- // workaround. in golang you can't assign to map in struct field
- tmp := w.watcherConfigs[srvName]
- tmp.Files = fileList
- w.watcherConfigs[srvName] = tmp
- }
- return nil
-}
-
-// ConvertIgnored is used to convert slice to map with ignored files
-func ConvertIgnored(ignored []string) (map[string]struct{}, error) {
- if len(ignored) == 0 {
- return nil, nil
- }
-
- ign := make(map[string]struct{}, len(ignored))
- for i := 0; i < len(ignored); i++ {
- abs, err := filepath.Abs(ignored[i])
- if err != nil {
- return nil, err
- }
- ign[abs] = struct{}{}
- }
-
- return ign, nil
-}
-
-// https://en.wikipedia.org/wiki/Inotify
-// SetMaxFileEvents sets max file notify events for Watcher
-// In case of file watch errors, this value can be increased system-wide
-// For linux: set --> fs.inotify.max_user_watches = 600000 (under /etc/<choose_name_here>.conf)
-// Add apply: sudo sysctl -p --system
-// func SetMaxFileEvents(events int) Options {
-// return func(watcher *Watcher) {
-// watcher.maxFileWatchEvents = events
-// }
-//
-// }
-
-// pass map from outside
-func (w *Watcher) retrieveFilesSingle(serviceName, path string) (map[string]os.FileInfo, error) {
- stat, err := os.Stat(path)
- if err != nil {
- return nil, err
- }
-
- filesList := make(map[string]os.FileInfo, 10)
- filesList[path] = stat
-
- // if it's not a dir, return
- if !stat.IsDir() {
- return filesList, nil
- }
-
- fileInfoList, err := ioutil.ReadDir(path)
- if err != nil {
- return nil, err
- }
-
- // recursive calls are slow in compare to goto
- // so, we will add files with goto pattern
-outer:
- for i := 0; i < len(fileInfoList); i++ {
- // if file in ignored --> continue
- if _, ignored := w.watcherConfigs[serviceName].Ignored[path]; ignored {
- continue
- }
-
- // if filename does not contain pattern --> ignore that file
- if w.watcherConfigs[serviceName].FilePatterns != nil && w.watcherConfigs[serviceName].FilterHooks != nil {
- err = w.watcherConfigs[serviceName].FilterHooks(fileInfoList[i].Name(), w.watcherConfigs[serviceName].FilePatterns)
- if errors.Is(errors.SkipFile, err) {
- continue outer
- }
- }
-
- filesList[fileInfoList[i].Name()] = fileInfoList[i]
- }
-
- return filesList, nil
-}
-
-func (w *Watcher) StartPolling(duration time.Duration) error {
- w.mu.Lock()
- const op = errors.Op("watcher_start_polling")
- if w.started {
- w.mu.Unlock()
- return errors.E(op, errors.Str("already started"))
- }
-
- w.started = true
- w.mu.Unlock()
-
- return w.waitEvent(duration)
-}
-
-// this is blocking operation
-func (w *Watcher) waitEvent(d time.Duration) error {
- ticker := time.NewTicker(d)
- for {
- select {
- case <-w.close:
- ticker.Stop()
- // just exit
- // no matter for the pollEvents
- return nil
- case <-ticker.C:
- // this is not very effective way
- // because we have to wait on Lock
- // better is to listen files in parallel, but, since that would be used in debug...
- for serviceName := range w.watcherConfigs {
- fileList, _ := w.retrieveFileList(serviceName, w.watcherConfigs[serviceName])
- w.pollEvents(w.watcherConfigs[serviceName].ServiceName, fileList)
- }
- }
- }
-}
-
-// retrieveFileList get file list for service
-func (w *Watcher) retrieveFileList(serviceName string, config WatcherConfig) (map[string]os.FileInfo, error) {
- fileList := make(map[string]os.FileInfo)
- if config.Recursive {
- // walk through directories recursively
- for i := 0; i < len(config.Directories); i++ {
- // full path is workdir/relative_path
- fullPath, err := filepath.Abs(config.Directories[i])
- if err != nil {
- return nil, err
- }
- list, err := w.retrieveFilesRecursive(serviceName, fullPath)
- if err != nil {
- return nil, err
- }
-
- for k := range list {
- fileList[k] = list[k]
- }
- }
- return fileList, nil
- }
-
- for i := 0; i < len(config.Directories); i++ {
- // full path is workdir/relative_path
- fullPath, err := filepath.Abs(config.Directories[i])
- if err != nil {
- return nil, err
- }
-
- // list is pathToFiles with files
- list, err := w.retrieveFilesSingle(serviceName, fullPath)
- if err != nil {
- return nil, err
- }
-
- for pathToFile, file := range list {
- fileList[pathToFile] = file
- }
- }
-
- return fileList, nil
-}
-
-func (w *Watcher) retrieveFilesRecursive(serviceName, root string) (map[string]os.FileInfo, error) {
- fileList := make(map[string]os.FileInfo)
-
- return fileList, filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
- const op = errors.Op("retrieve files recursive")
- if err != nil {
- return errors.E(op, err)
- }
-
- // If path is ignored and it's a directory, skip the directory. If it's
- // ignored and it's a single file, skip the file.
- _, ignored := w.watcherConfigs[serviceName].Ignored[path]
- if ignored {
- if info.IsDir() {
- // if it's dir, ignore whole
- return filepath.SkipDir
- }
- return nil
- }
-
- // if filename does not contain pattern --> ignore that file
- err = w.watcherConfigs[serviceName].FilterHooks(info.Name(), w.watcherConfigs[serviceName].FilePatterns)
- if errors.Is(errors.SkipFile, err) {
- return nil
- }
-
- // Add the path and it's info to the file list.
- fileList[path] = info
- return nil
- })
-}
-
-func (w *Watcher) pollEvents(serviceName string, files map[string]os.FileInfo) {
- w.mu.Lock()
- defer w.mu.Unlock()
-
- // InsertMany create and remove events for use to check for rename events.
- creates := make(map[string]os.FileInfo)
- removes := make(map[string]os.FileInfo)
-
- // Check for removed files.
- for pth := range w.watcherConfigs[serviceName].Files {
- if _, found := files[pth]; !found {
- removes[pth] = w.watcherConfigs[serviceName].Files[pth]
- w.log.Debug("file added to the list of removed files", "path", pth, "name", w.watcherConfigs[serviceName].Files[pth].Name(), "size", w.watcherConfigs[serviceName].Files[pth].Size())
- }
- }
-
- // Check for created files, writes and chmods.
- for pth := range files {
- if files[pth].IsDir() {
- continue
- }
- oldInfo, found := w.watcherConfigs[serviceName].Files[pth]
- if !found {
- // A file was created.
- creates[pth] = files[pth]
- w.log.Debug("file was created", "path", pth, "name", files[pth].Name(), "size", files[pth].Size())
- continue
- }
-
- if oldInfo.ModTime() != files[pth].ModTime() || oldInfo.Mode() != files[pth].Mode() {
- w.watcherConfigs[serviceName].Files[pth] = files[pth]
- w.log.Debug("file was updated", "path", pth, "name", files[pth].Name(), "size", files[pth].Size())
- w.Event <- Event{
- Path: pth,
- Info: files[pth],
- service: serviceName,
- }
- }
- }
-
- // Send all the remaining create and remove events.
- for pth := range creates {
- // add file to the plugin watch files
- w.watcherConfigs[serviceName].Files[pth] = creates[pth]
- w.log.Debug("file was added to watcher", "path", pth, "name", creates[pth].Name(), "size", creates[pth].Size())
-
- w.Event <- Event{
- Path: pth,
- Info: creates[pth],
- service: serviceName,
- }
- }
-
- for pth := range removes {
- // delete path from the config
- delete(w.watcherConfigs[serviceName].Files, pth)
- w.log.Debug("file was removed from watcher", "path", pth, "name", removes[pth].Name(), "size", removes[pth].Size())
-
- w.Event <- Event{
- Path: pth,
- Info: removes[pth],
- service: serviceName,
- }
- }
-}
-
-func (w *Watcher) Stop() {
- w.close <- struct{}{}
-}
diff --git a/plugins/resetter/interface.go b/plugins/resetter/interface.go
deleted file mode 100644
index 0defcaba..00000000
--- a/plugins/resetter/interface.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package resetter
-
-// Resetter interface
-type Resetter interface {
- // Reset reload plugin
- Reset() error
-}
diff --git a/plugins/resetter/plugin.go b/plugins/resetter/plugin.go
deleted file mode 100644
index 191185ae..00000000
--- a/plugins/resetter/plugin.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package resetter
-
-import (
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
-)
-
-const PluginName = "resetter"
-
-type Plugin struct {
- registry map[string]Resetter
-}
-
-func (p *Plugin) Init() error {
- p.registry = make(map[string]Resetter)
- return nil
-}
-
-// Reset named service.
-func (p *Plugin) Reset(name string) error {
- const op = errors.Op("resetter_plugin_reset_by_name")
- svc, ok := p.registry[name]
- if !ok {
- return errors.E(op, errors.Errorf("no such plugin: %s", name))
- }
-
- return svc.Reset()
-}
-
-// RegisterTarget resettable service.
-func (p *Plugin) RegisterTarget(name endure.Named, r Resetter) error {
- p.registry[name.Name()] = r
- return nil
-}
-
-// Collects declares services to be collected.
-func (p *Plugin) Collects() []interface{} {
- return []interface{}{
- p.RegisterTarget,
- }
-}
-
-// Name of the service.
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (p *Plugin) Available() {
-}
-
-// RPC returns associated rpc service.
-func (p *Plugin) RPC() interface{} {
- return &rpc{srv: p}
-}
diff --git a/plugins/resetter/rpc.go b/plugins/resetter/rpc.go
deleted file mode 100644
index 79171b5c..00000000
--- a/plugins/resetter/rpc.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package resetter
-
-import "github.com/spiral/errors"
-
-type rpc struct {
- srv *Plugin
-}
-
-// List all resettable plugins.
-func (rpc *rpc) List(_ bool, list *[]string) error {
- *list = make([]string, 0)
-
- for name := range rpc.srv.registry {
- *list = append(*list, name)
- }
- return nil
-}
-
-// Reset named plugin.
-func (rpc *rpc) Reset(service string, done *bool) error {
- const op = errors.Op("resetter_rpc_reset")
- err := rpc.srv.Reset(service)
- if err != nil {
- *done = false
- return errors.E(op, err)
- }
- *done = true
- return nil
-}
diff --git a/plugins/rpc/config.go b/plugins/rpc/config.go
deleted file mode 100644
index 88ad7f0e..00000000
--- a/plugins/rpc/config.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package rpc
-
-import (
- "errors"
- "net"
- "strings"
-
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-// Config defines RPC service config.
-type Config struct {
- // Listen string
- Listen string
-}
-
-// InitDefaults allows to init blank config with pre-defined set of default values.
-func (c *Config) InitDefaults() {
- if c.Listen == "" {
- c.Listen = "tcp://127.0.0.1:6001"
- }
-}
-
-// Valid returns nil if config is valid.
-func (c *Config) Valid() error {
- if dsn := strings.Split(c.Listen, "://"); len(dsn) != 2 {
- return errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)")
- }
-
- return nil
-}
-
-// Listener creates new rpc socket Listener.
-func (c *Config) Listener() (net.Listener, error) {
- return utils.CreateListener(c.Listen)
-}
-
-// Dialer creates rpc socket Dialer.
-func (c *Config) Dialer() (net.Conn, error) {
- dsn := strings.Split(c.Listen, "://")
- if len(dsn) != 2 {
- return nil, errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)")
- }
-
- return net.Dial(dsn[0], dsn[1])
-}
diff --git a/plugins/rpc/doc/plugin_arch.drawio b/plugins/rpc/doc/plugin_arch.drawio
deleted file mode 100644
index dec5f0b2..00000000
--- a/plugins/rpc/doc/plugin_arch.drawio
+++ /dev/null
@@ -1 +0,0 @@
-<mxfile host="Electron" modified="2020-10-19T17:14:19.125Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.7.9 Chrome/85.0.4183.121 Electron/10.1.3 Safari/537.36" etag="2J39x4EyFr1zaE9BXKM4" version="13.7.9" type="device"><diagram id="q2oMKs6VHyn7y0AfAXBL" name="Page-1">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</diagram></mxfile> \ No newline at end of file
diff --git a/plugins/rpc/interface.go b/plugins/rpc/interface.go
deleted file mode 100644
index eb6da9af..00000000
--- a/plugins/rpc/interface.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package rpc
-
-// RPCer declares the ability to create set of public RPC methods.
-type RPCer interface {
- // RPC Provides methods for the given service.
- RPC() interface{}
-}
diff --git a/plugins/rpc/plugin.go b/plugins/rpc/plugin.go
deleted file mode 100644
index b8ee6d13..00000000
--- a/plugins/rpc/plugin.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package rpc
-
-import (
- "net"
- "net/rpc"
- "sync/atomic"
-
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
- goridgeRpc "github.com/spiral/goridge/v3/pkg/rpc"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-// PluginName contains default plugin name.
-const PluginName = "rpc"
-
-// Plugin is RPC service.
-type Plugin struct {
- cfg Config
- log logger.Logger
- rpc *rpc.Server
- // set of the plugins, which are implement RPCer interface and can be plugged into the RR via RPC
- plugins map[string]RPCer
- listener net.Listener
- closed uint32
-}
-
-// Init rpc service. Must return true if service is enabled.
-func (s *Plugin) Init(cfg config.Configurer, log logger.Logger) error {
- const op = errors.Op("rpc_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(PluginName, &s.cfg)
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
- // Init defaults
- s.cfg.InitDefaults()
- // Init pluggable plugins map
- s.plugins = make(map[string]RPCer, 5)
- // init logs
- s.log = log
-
- // set up state
- atomic.StoreUint32(&s.closed, 0)
-
- // validate config
- err = s.cfg.Valid()
- if err != nil {
- return errors.E(op, err)
- }
- return nil
-}
-
-// Serve serves the service.
-func (s *Plugin) Serve() chan error {
- const op = errors.Op("rpc_plugin_serve")
- errCh := make(chan error, 1)
-
- s.rpc = rpc.NewServer()
-
- plugins := make([]string, 0, len(s.plugins))
-
- // Attach all services
- for name := range s.plugins {
- err := s.Register(name, s.plugins[name].RPC())
- if err != nil {
- errCh <- errors.E(op, err)
- return errCh
- }
-
- plugins = append(plugins, name)
- }
-
- var err error
- s.listener, err = s.cfg.Listener()
- if err != nil {
- errCh <- errors.E(op, err)
- return errCh
- }
-
- s.log.Debug("Started RPC service", "address", s.cfg.Listen, "plugins", plugins)
-
- go func() {
- for {
- conn, err := s.listener.Accept()
- if err != nil {
- if atomic.LoadUint32(&s.closed) == 1 {
- // just continue, this is not a critical issue, we just called Stop
- return
- }
-
- s.log.Error("listener accept error", "error", err)
- errCh <- errors.E(errors.Op("listener accept"), errors.Serve, err)
- return
- }
-
- go s.rpc.ServeCodec(goridgeRpc.NewCodec(conn))
- }
- }()
-
- return errCh
-}
-
-// Stop stops the service.
-func (s *Plugin) Stop() error {
- const op = errors.Op("rpc_plugin_stop")
- // store closed state
- atomic.StoreUint32(&s.closed, 1)
- err := s.listener.Close()
- if err != nil {
- return errors.E(op, err)
- }
- return nil
-}
-
-// Name contains service name.
-func (s *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (s *Plugin) Available() {
-}
-
-// Collects all plugins which implement Name + RPCer interfaces
-func (s *Plugin) Collects() []interface{} {
- return []interface{}{
- s.RegisterPlugin,
- }
-}
-
-// RegisterPlugin registers RPC service plugin.
-func (s *Plugin) RegisterPlugin(name endure.Named, p RPCer) {
- s.plugins[name.Name()] = p
-}
-
-// Register publishes in the server the set of methods of the
-// receiver value that satisfy the following conditions:
-// - exported method of exported type
-// - two arguments, both of exported type
-// - the second argument is a pointer
-// - one return value, of type error
-// It returns an error if the receiver is not an exported type or has
-// no suitable methods. It also logs the error using package log.
-func (s *Plugin) Register(name string, svc interface{}) error {
- if s.rpc == nil {
- return errors.E("RPC service is not configured")
- }
-
- return s.rpc.RegisterName(name, svc)
-}
diff --git a/plugins/server/command.go b/plugins/server/command.go
deleted file mode 100644
index b8bc1395..00000000
--- a/plugins/server/command.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package server
-
-import (
- "os"
- "regexp"
-
- "github.com/spiral/errors"
-)
-
-// pattern for the path finding
-const pattern string = `^\/*([A-z/.:-]+\.(php|sh|ph))$`
-
-func (server *Plugin) scanCommand(cmd []string) error {
- const op = errors.Op("server_command_scan")
- r, err := regexp.Compile(pattern)
- if err != nil {
- return err
- }
-
- for i := 0; i < len(cmd); i++ {
- if r.MatchString(cmd[i]) {
- // try to stat
- _, err := os.Stat(cmd[i])
- if err != nil {
- return errors.E(op, errors.FileNotFound, err)
- }
-
- // stat successful
- return nil
- }
- }
- return errors.E(errors.Str("scan failed, possible path not found, this is not an error"), op)
-}
diff --git a/plugins/server/command_test.go b/plugins/server/command_test.go
deleted file mode 100644
index 74762ccd..00000000
--- a/plugins/server/command_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package server
-
-import (
- "strings"
- "testing"
-
- "github.com/spiral/errors"
- "github.com/stretchr/testify/assert"
-)
-
-func TestServerCommandChecker(t *testing.T) {
- s := &Plugin{}
- cmd1 := "php ../../tests/client.php"
- assert.NoError(t, s.scanCommand(strings.Split(cmd1, " ")))
-
- cmd2 := "C:/../../abcdef/client.php"
- assert.Error(t, s.scanCommand(strings.Split(cmd2, " ")))
-
- cmd3 := "sh ./script.sh"
- err := s.scanCommand(strings.Split(cmd3, " "))
- assert.Error(t, err)
- if !errors.Is(errors.FileNotFound, err) {
- t.Fatal("should be of filenotfound type")
- }
-
- cmd4 := "php ../../tests/client.php --option1 --option2"
- err = s.scanCommand(strings.Split(cmd4, " "))
- assert.NoError(t, err)
-
- cmd5 := "php ../../tests/cluent.php --option1 --option2"
- err = s.scanCommand(strings.Split(cmd5, " "))
- assert.Error(t, err)
- if !errors.Is(errors.FileNotFound, err) {
- t.Fatal("should be of filenotfound type")
- }
-
- cmd6 := "php 0/../../tests/cluent.php --option1 --option2"
- err = s.scanCommand(strings.Split(cmd6, " "))
- assert.Error(t, err)
- if errors.Is(errors.FileNotFound, err) {
- t.Fatal("should be of filenotfound type")
- }
-}
diff --git a/plugins/server/config.go b/plugins/server/config.go
deleted file mode 100644
index 00ce4140..00000000
--- a/plugins/server/config.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package server
-
-import (
- "time"
-)
-
-// Config All config (.rr.yaml)
-// For other section use pointer to distinguish between `empty` and `not present`
-type Config struct {
- // Server config section
- Server struct {
- // Command to run as application.
- Command string `mapstructure:"command"`
- // User to run application under.
- User string `mapstructure:"user"`
- // Group to run application under.
- Group string `mapstructure:"group"`
- // Env represents application environment.
- Env Env `mapstructure:"env"`
- // Relay defines connection method and factory to be used to connect to workers:
- // "pipes", "tcp://:6001", "unix://rr.sock"
- // This config section must not change on re-configuration.
- Relay string `mapstructure:"relay"`
- // RelayTimeout defines for how long socket factory will be waiting for worker connection. This config section
- // must not change on re-configuration. Defaults to 60s.
- RelayTimeout time.Duration `mapstructure:"relay_timeout"`
- } `mapstructure:"server"`
-
- // we just need to know if the section exist, we don't need to read config from it
- RPC *struct {
- Listen string `mapstructure:"listen"`
- } `mapstructure:"rpc"`
- Logs *struct {
- } `mapstructure:"logs"`
- HTTP *struct {
- } `mapstructure:"http"`
- Redis *struct {
- } `mapstructure:"redis"`
- Boltdb *struct {
- } `mapstructure:"boltdb"`
- Memcached *struct {
- } `mapstructure:"memcached"`
- Memory *struct {
- } `mapstructure:"memory"`
- Metrics *struct {
- } `mapstructure:"metrics"`
- Reload *struct {
- } `mapstructure:"reload"`
-}
-
-// InitDefaults for the server config
-func (cfg *Config) InitDefaults() {
- if cfg.Server.Relay == "" {
- cfg.Server.Relay = "pipes"
- }
-
- if cfg.Server.RelayTimeout == 0 {
- cfg.Server.RelayTimeout = time.Second * 60
- }
-}
diff --git a/plugins/server/interface.go b/plugins/server/interface.go
deleted file mode 100644
index b0f84a7f..00000000
--- a/plugins/server/interface.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package server
-
-import (
- "context"
- "os/exec"
-
- "github.com/spiral/roadrunner/v2/pkg/events"
- "github.com/spiral/roadrunner/v2/pkg/pool"
- "github.com/spiral/roadrunner/v2/pkg/worker"
-)
-
-// Env variables type alias
-type Env map[string]string
-
-// Server creates workers for the application.
-type Server interface {
- // CmdFactory return a new command based on the .rr.yaml server.command section
- CmdFactory(env Env) (func() *exec.Cmd, error)
- // NewWorker return a new worker with provided and attached by the user listeners and environment variables
- NewWorker(ctx context.Context, env Env, listeners ...events.Listener) (*worker.Process, error)
- // NewWorkerPool return new pool of workers (PHP) with attached events listeners, env variables and based on the provided configuration
- NewWorkerPool(ctx context.Context, opt *pool.Config, env Env, listeners ...events.Listener) (pool.Pool, error)
-}
diff --git a/plugins/server/plugin.go b/plugins/server/plugin.go
deleted file mode 100644
index 5f5f2df9..00000000
--- a/plugins/server/plugin.go
+++ /dev/null
@@ -1,268 +0,0 @@
-package server
-
-import (
- "context"
- "fmt"
- "os"
- "os/exec"
- "strings"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/transport"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-
- // core imports
- "github.com/spiral/roadrunner/v2/pkg/events"
- "github.com/spiral/roadrunner/v2/pkg/pool"
- "github.com/spiral/roadrunner/v2/pkg/transport/pipe"
- "github.com/spiral/roadrunner/v2/pkg/transport/socket"
- "github.com/spiral/roadrunner/v2/pkg/worker"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-const (
- // PluginName for the server
- PluginName = "server"
- // RrRelay env variable key (internal)
- RrRelay = "RR_RELAY"
- // RrRPC env variable key (internal) if the RPC presents
- RrRPC = "RR_RPC"
-)
-
-// Plugin manages worker
-type Plugin struct {
- cfg Config
- log logger.Logger
- factory transport.Factory
-}
-
-// Init application provider.
-func (server *Plugin) Init(cfg config.Configurer, log logger.Logger) error {
- const op = errors.Op("server_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
- err := cfg.Unmarshal(&server.cfg)
- if err != nil {
- return errors.E(op, errors.Init, err)
- }
- server.cfg.InitDefaults()
- server.log = log
-
- return nil
-}
-
-// Name contains service name.
-func (server *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (server *Plugin) Available() {}
-
-// Serve (Start) server plugin (just a mock here to satisfy interface)
-func (server *Plugin) Serve() chan error {
- const op = errors.Op("server_plugin_serve")
- errCh := make(chan error, 1)
- var err error
- server.factory, err = server.initFactory()
- if err != nil {
- errCh <- errors.E(op, err)
- return errCh
- }
- return errCh
-}
-
-// Stop used to close chosen in config factory
-func (server *Plugin) Stop() error {
- if server.factory == nil {
- return nil
- }
-
- return server.factory.Close()
-}
-
-// CmdFactory provides worker command factory associated with given context.
-func (server *Plugin) CmdFactory(env Env) (func() *exec.Cmd, error) {
- const op = errors.Op("server_plugin_cmd_factory")
- var cmdArgs []string
-
- // create command according to the config
- cmdArgs = append(cmdArgs, strings.Split(server.cfg.Server.Command, " ")...)
- if len(cmdArgs) < 2 {
- return nil, errors.E(op, errors.Str("minimum command should be `<executable> <script>"))
- }
-
- // try to find a path here
- err := server.scanCommand(cmdArgs)
- if err != nil {
- server.log.Info("scan command", "reason", err)
- }
-
- return func() *exec.Cmd {
- cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) //nolint:gosec
- utils.IsolateProcess(cmd)
-
- // if user is not empty, and OS is linux or macos
- // execute php worker from that particular user
- if server.cfg.Server.User != "" {
- err := utils.ExecuteFromUser(cmd, server.cfg.Server.User)
- if err != nil {
- return nil
- }
- }
-
- cmd.Env = server.setEnv(env)
-
- return cmd
- }, nil
-}
-
-// NewWorker issues new standalone worker.
-func (server *Plugin) NewWorker(ctx context.Context, env Env, listeners ...events.Listener) (*worker.Process, error) {
- const op = errors.Op("server_plugin_new_worker")
-
- list := make([]events.Listener, 0, len(listeners))
- list = append(list, server.collectWorkerEvents)
-
- spawnCmd, err := server.CmdFactory(env)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- w, err := server.factory.SpawnWorkerWithTimeout(ctx, spawnCmd(), list...)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return w, nil
-}
-
-// NewWorkerPool issues new worker pool.
-func (server *Plugin) NewWorkerPool(ctx context.Context, opt *pool.Config, env Env, listeners ...events.Listener) (pool.Pool, error) {
- const op = errors.Op("server_plugin_new_worker_pool")
-
- spawnCmd, err := server.CmdFactory(env)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- list := make([]events.Listener, 0, 2)
- list = append(list, server.collectPoolEvents, server.collectWorkerEvents)
- if len(listeners) != 0 {
- list = append(list, listeners...)
- }
-
- p, err := pool.Initialize(ctx, spawnCmd, server.factory, opt, pool.AddListeners(list...))
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return p, nil
-}
-
-// creates relay and worker factory.
-func (server *Plugin) initFactory() (transport.Factory, error) {
- const op = errors.Op("server_plugin_init_factory")
- if server.cfg.Server.Relay == "" || server.cfg.Server.Relay == "pipes" {
- return pipe.NewPipeFactory(), nil
- }
-
- dsn := strings.Split(server.cfg.Server.Relay, "://")
- if len(dsn) != 2 {
- return nil, errors.E(op, errors.Network, errors.Str("invalid DSN (tcp://:6001, unix://file.sock)"))
- }
-
- lsn, err := utils.CreateListener(server.cfg.Server.Relay)
- if err != nil {
- return nil, errors.E(op, errors.Network, err)
- }
-
- switch dsn[0] {
- // sockets group
- case "unix":
- return socket.NewSocketServer(lsn, server.cfg.Server.RelayTimeout), nil
- case "tcp":
- return socket.NewSocketServer(lsn, server.cfg.Server.RelayTimeout), nil
- default:
- return nil, errors.E(op, errors.Network, errors.Str("invalid DSN (tcp://:6001, unix://file.sock)"))
- }
-}
-
-func (server *Plugin) setEnv(e Env) []string {
- env := append(os.Environ(), fmt.Sprintf(RrRelay+"=%s", server.cfg.Server.Relay))
- for k, v := range e {
- env = append(env, fmt.Sprintf("%s=%s", strings.ToUpper(k), v))
- }
-
- if server.cfg.RPC != nil && server.cfg.RPC.Listen != "" {
- env = append(env, fmt.Sprintf("%s=%s", RrRPC, server.cfg.RPC.Listen))
- }
-
- // set env variables from the config
- if len(server.cfg.Server.Env) > 0 {
- for k, v := range server.cfg.Server.Env {
- env = append(env, fmt.Sprintf("%s=%s", strings.ToUpper(k), v))
- }
- }
-
- return env
-}
-
-func (server *Plugin) collectPoolEvents(event interface{}) {
- if we, ok := event.(events.PoolEvent); ok {
- switch we.Event {
- case events.EventMaxMemory:
- server.log.Warn("worker max memory reached", "pid", we.Payload.(worker.BaseProcess).Pid())
- case events.EventNoFreeWorkers:
- server.log.Warn("no free workers in the pool", "error", we.Payload.(error).Error())
- case events.EventPoolError:
- server.log.Error("pool error", "error", we.Payload.(error).Error())
- case events.EventSupervisorError:
- server.log.Error("pool supervisor error", "error", we.Payload.(error).Error())
- case events.EventTTL:
- server.log.Warn("worker TTL reached", "pid", we.Payload.(worker.BaseProcess).Pid())
- case events.EventWorkerConstruct:
- if _, ok := we.Payload.(error); ok {
- server.log.Error("worker construction error", "error", we.Payload.(error).Error())
- return
- }
- server.log.Debug("worker constructed", "pid", we.Payload.(worker.BaseProcess).Pid())
- case events.EventWorkerDestruct:
- server.log.Debug("worker destructed", "pid", we.Payload.(worker.BaseProcess).Pid())
- case events.EventExecTTL:
- server.log.Warn("worker exec timeout reached", "error", we.Payload.(error).Error())
- case events.EventIdleTTL:
- server.log.Warn("worker idle timeout reached", "pid", we.Payload.(worker.BaseProcess).Pid())
- case events.EventPoolRestart:
- server.log.Warn("requested pool restart")
- }
- }
-}
-
-func (server *Plugin) collectWorkerEvents(event interface{}) {
- if we, ok := event.(events.WorkerEvent); ok {
- switch we.Event {
- case events.EventWorkerError:
- switch e := we.Payload.(type) { //nolint:gocritic
- case error:
- if errors.Is(errors.SoftJob, e) {
- // get source error for the softjob error
- server.log.Error(strings.TrimRight(e.(*errors.Error).Err.Error(), " \n\t"))
- return
- }
-
- // print full error for the other types of errors
- server.log.Error(strings.TrimRight(e.Error(), " \n\t"))
- return
- }
- server.log.Error(strings.TrimRight(we.Payload.(error).Error(), " \n\t"))
- case events.EventWorkerLog:
- server.log.Debug(strings.TrimRight(utils.AsString(we.Payload.([]byte)), " \n\t"))
- // stderr event is INFO level
- case events.EventWorkerStderr:
- server.log.Info(strings.TrimRight(utils.AsString(we.Payload.([]byte)), " \n\t"))
- }
- }
-}
diff --git a/plugins/service/config.go b/plugins/service/config.go
deleted file mode 100644
index 871c8f76..00000000
--- a/plugins/service/config.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package service
-
-import "time"
-
-// Service represents particular service configuration
-type Service struct {
- Command string `mapstructure:"command"`
- ProcessNum int `mapstructure:"process_num"`
- ExecTimeout time.Duration `mapstructure:"exec_timeout"`
- RemainAfterExit bool `mapstructure:"remain_after_exit"`
- RestartSec uint64 `mapstructure:"restart_sec"`
-}
-
-// Config for the services
-type Config struct {
- Services map[string]Service `mapstructure:"service"`
-}
-
-func (c *Config) InitDefault() {
- if len(c.Services) > 0 {
- for k, v := range c.Services {
- if v.ProcessNum == 0 {
- val := c.Services[k]
- val.ProcessNum = 1
- c.Services[k] = val
- }
- if v.RestartSec == 0 {
- val := c.Services[k]
- val.RestartSec = 30
- c.Services[k] = val
- }
- }
- }
-}
diff --git a/plugins/service/plugin.go b/plugins/service/plugin.go
deleted file mode 100644
index 3bd0f956..00000000
--- a/plugins/service/plugin.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package service
-
-import (
- "sync"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/state/process"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const PluginName string = "service"
-
-type Plugin struct {
- sync.Mutex
-
- logger logger.Logger
- cfg Config
-
- // all processes attached to the service
- processes []*Process
-}
-
-func (service *Plugin) Init(cfg config.Configurer, log logger.Logger) error {
- const op = errors.Op("service_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(errors.Disabled)
- }
- err := cfg.UnmarshalKey(PluginName, &service.cfg.Services)
- if err != nil {
- return errors.E(op, err)
- }
-
- // init default parameters if not set by user
- service.cfg.InitDefault()
- // save the logger
- service.logger = log
-
- return nil
-}
-
-func (service *Plugin) Serve() chan error {
- errCh := make(chan error, 1)
-
- // start processing
- go func() {
- // lock here, because Stop command might be invoked during the Serve
- service.Lock()
- defer service.Unlock()
-
- service.processes = make([]*Process, 0, len(service.cfg.Services))
- // for the every service
- for k := range service.cfg.Services {
- // create needed number of the processes
- for i := 0; i < service.cfg.Services[k].ProcessNum; i++ {
- // create processor structure, which will process all the services
- service.processes = append(service.processes, NewServiceProcess(
- service.cfg.Services[k].RemainAfterExit,
- service.cfg.Services[k].ExecTimeout,
- service.cfg.Services[k].RestartSec,
- service.cfg.Services[k].Command,
- service.logger,
- errCh,
- ))
- }
- }
-
- // start all processes
- for i := 0; i < len(service.processes); i++ {
- service.processes[i].start()
- }
- }()
-
- return errCh
-}
-
-func (service *Plugin) Workers() []process.State {
- service.Lock()
- defer service.Unlock()
- states := make([]process.State, 0, len(service.processes))
- for i := 0; i < len(service.processes); i++ {
- st, err := process.GeneralProcessState(service.processes[i].Pid, service.processes[i].rawCmd)
- if err != nil {
- continue
- }
- states = append(states, st)
- }
- return states
-}
-
-func (service *Plugin) Stop() error {
- service.Lock()
- defer service.Unlock()
-
- if len(service.processes) > 0 {
- for i := 0; i < len(service.processes); i++ {
- service.processes[i].stop()
- }
- }
- return nil
-}
-
-// Name contains service name.
-func (service *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (service *Plugin) Available() {
-}
diff --git a/plugins/service/process.go b/plugins/service/process.go
deleted file mode 100644
index cac5c41e..00000000
--- a/plugins/service/process.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package service
-
-import (
- "os/exec"
- "strings"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-// Process structure contains an information about process, restart information, log, errors, etc
-type Process struct {
- sync.Mutex
- // command to execute
- command *exec.Cmd
- // rawCmd from the plugin
- rawCmd string
- Pid int
-
- // root plugin error chan
- errCh chan error
- // logger
- log logger.Logger
-
- ExecTimeout time.Duration
- RemainAfterExit bool
- RestartSec uint64
-
- // process start time
- startTime time.Time
- stopped uint64
-}
-
-// NewServiceProcess constructs service process structure
-func NewServiceProcess(restartAfterExit bool, execTimeout time.Duration, restartDelay uint64, command string, l logger.Logger, errCh chan error) *Process {
- return &Process{
- rawCmd: command,
- RestartSec: restartDelay,
- ExecTimeout: execTimeout,
- RemainAfterExit: restartAfterExit,
- errCh: errCh,
- log: l,
- }
-}
-
-// write message to the log (stderr)
-func (p *Process) Write(b []byte) (int, error) {
- p.log.Info(utils.AsString(b))
- return len(b), nil
-}
-
-func (p *Process) start() {
- p.Lock()
- defer p.Unlock()
- const op = errors.Op("processor_start")
-
- // crate fat-process here
- p.createProcess()
-
- // non blocking process start
- err := p.command.Start()
- if err != nil {
- p.errCh <- errors.E(op, err)
- return
- }
-
- // start process waiting routine
- go p.wait()
- // execHandler checks for the execTimeout
- go p.execHandler()
- // save start time
- p.startTime = time.Now()
- p.Pid = p.command.Process.Pid
-}
-
-// create command for the process
-func (p *Process) createProcess() {
- // cmdArgs contain command arguments if the command in form of: php <command> or ls <command> -i -b
- var cmdArgs []string
- cmdArgs = append(cmdArgs, strings.Split(p.rawCmd, " ")...)
- if len(cmdArgs) < 2 {
- p.command = exec.Command(p.rawCmd) //nolint:gosec
- } else {
- p.command = exec.Command(cmdArgs[0], cmdArgs[1:]...) //nolint:gosec
- }
- // redirect stderr into the Write function of the process.go
- p.command.Stderr = p
-}
-
-// wait process for exit
-func (p *Process) wait() {
- // Wait error doesn't matter here
- err := p.command.Wait()
- if err != nil {
- p.log.Error("process wait error", "error", err)
- }
- // wait for restart delay
- if p.RemainAfterExit {
- // wait for the delay
- time.Sleep(time.Second * time.Duration(p.RestartSec))
- // and start command again
- p.start()
- }
-}
-
-// stop can be only sent by the Endure when plugin stopped
-func (p *Process) stop() {
- atomic.StoreUint64(&p.stopped, 1)
-}
-
-func (p *Process) execHandler() {
- tt := time.NewTicker(time.Second)
- for range tt.C {
- // lock here, because p.startTime could be changed during the check
- p.Lock()
- // if the exec timeout is set
- if p.ExecTimeout != 0 {
- // if stopped -> kill the process (SIGINT-> SIGKILL) and exit
- if atomic.CompareAndSwapUint64(&p.stopped, 1, 1) {
- err := p.command.Process.Signal(syscall.SIGINT)
- if err != nil {
- _ = p.command.Process.Signal(syscall.SIGKILL)
- }
- tt.Stop()
- p.Unlock()
- return
- }
-
- // check the running time for the script
- if time.Now().After(p.startTime.Add(p.ExecTimeout)) {
- err := p.command.Process.Signal(syscall.SIGINT)
- if err != nil {
- _ = p.command.Process.Signal(syscall.SIGKILL)
- }
- p.Unlock()
- tt.Stop()
- return
- }
- }
- p.Unlock()
- }
-}
diff --git a/plugins/sqs/config.go b/plugins/sqs/config.go
deleted file mode 100644
index 9b2a1ca8..00000000
--- a/plugins/sqs/config.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package sqs
-
-import "github.com/aws/aws-sdk-go-v2/aws"
-
-const (
- attributes string = "attributes"
- tags string = "tags"
- queue string = "queue"
- pref string = "prefetch"
- visibility string = "visibility_timeout"
- waitTime string = "wait_time"
-)
-
-type GlobalCfg struct {
- Key string `mapstructure:"key"`
- Secret string `mapstructure:"secret"`
- Region string `mapstructure:"region"`
- SessionToken string `mapstructure:"session_token"`
- Endpoint string `mapstructure:"endpoint"`
-}
-
-// Config is used to parse pipeline configuration
-type Config struct {
- // The duration (in seconds) that the received messages are hidden from subsequent
- // retrieve requests after being retrieved by a ReceiveMessage request.
- VisibilityTimeout int32 `mapstructure:"visibility_timeout"`
- // 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.
- WaitTimeSeconds int32 `mapstructure:"wait_time_seconds"`
- // Prefetch is the maximum number of messages to return. Amazon SQS never returns more messages
- // than this value (however, fewer messages might be returned). Valid values: 1 to
- // 10. Default: 1.
- Prefetch int32 `mapstructure:"prefetch"`
- // The name of the new queue. The following limits apply to this name:
- //
- // * A queue
- // name can have up to 80 characters.
- //
- // * Valid values: alphanumeric characters,
- // hyphens (-), and underscores (_).
- //
- // * A FIFO queue name must end with the .fifo
- // suffix.
- //
- // Queue URLs and names are case-sensitive.
- //
- // This member is required.
- Queue *string `mapstructure:"queue"`
-
- // A map of attributes with their corresponding values. The following lists the
- // names, descriptions, and values of the special request parameters that the
- // CreateQueue action uses.
- // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SetQueueAttributes.html
- Attributes map[string]string `mapstructure:"attributes"`
-
- // From amazon docs:
- // Add cost allocation tags to the specified Amazon SQS queue. For an overview, see
- // Tagging Your Amazon SQS Queues
- // (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-queue-tags.html)
- // in the Amazon SQS Developer Guide. When you use queue tags, keep the following
- // guidelines in mind:
- //
- // * Adding more than 50 tags to a queue isn't recommended.
- //
- // *
- // Tags don't have any semantic meaning. Amazon SQS interprets tags as character
- // strings.
- //
- // * Tags are case-sensitive.
- //
- // * A new tag with a key identical to that
- // of an existing tag overwrites the existing tag.
- //
- // For a full list of tag
- // restrictions, see Quotas related to queues
- // (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-limits.html#limits-queues)
- // in the Amazon SQS Developer Guide. To be able to tag a queue on creation, you
- // must have the sqs:CreateQueue and sqs:TagQueue permissions. Cross-account
- // permissions don't apply to this action. For more information, see Grant
- // cross-account permissions to a role and a user name
- // (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
- // in the Amazon SQS Developer Guide.
- Tags map[string]string `mapstructure:"tags"`
-}
-
-func (c *GlobalCfg) InitDefault() {
- if c.Endpoint == "" {
- c.Endpoint = "http://127.0.0.1:9324"
- }
-}
-
-func (c *Config) InitDefault() {
- if c.Queue == nil {
- c.Queue = aws.String("default")
- }
-
- if c.Prefetch == 0 || c.Prefetch > 10 {
- c.Prefetch = 10
- }
-
- if c.WaitTimeSeconds == 0 {
- c.WaitTimeSeconds = 5
- }
-
- if c.Attributes == nil {
- c.Attributes = make(map[string]string)
- }
-
- if c.Tags == nil {
- c.Tags = make(map[string]string)
- }
-}
diff --git a/plugins/sqs/consumer.go b/plugins/sqs/consumer.go
deleted file mode 100644
index 92dbd6a8..00000000
--- a/plugins/sqs/consumer.go
+++ /dev/null
@@ -1,421 +0,0 @@
-package sqs
-
-import (
- "context"
- "strconv"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/aws/retry"
- "github.com/aws/aws-sdk-go-v2/config"
- "github.com/aws/aws-sdk-go-v2/credentials"
- "github.com/aws/aws-sdk-go-v2/service/sqs"
- "github.com/aws/aws-sdk-go-v2/service/sqs/types"
- "github.com/google/uuid"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- jobState "github.com/spiral/roadrunner/v2/pkg/state/job"
- cfgPlugin "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-type consumer struct {
- sync.Mutex
- pq priorityqueue.Queue
- log logger.Logger
- eh events.Handler
- pipeline atomic.Value
-
- // connection info
- key string
- secret string
- sessionToken string
- region string
- endpoint string
- queue *string
- messageGroupID string
- waitTime int32
- prefetch int32
- visibilityTimeout int32
-
- // if user invoke several resume operations
- listeners uint32
-
- // queue optional parameters
- attributes map[string]string
- tags map[string]string
-
- client *sqs.Client
- queueURL *string
-
- pauseCh chan struct{}
-}
-
-func NewSQSConsumer(configKey string, log logger.Logger, cfg cfgPlugin.Configurer, e events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("new_sqs_consumer")
-
- // if no such key - error
- if !cfg.Has(configKey) {
- return nil, errors.E(op, errors.Errorf("no configuration by provided key: %s", configKey))
- }
-
- // if no global section
- if !cfg.Has(pluginName) {
- return nil, errors.E(op, errors.Str("no global sqs configuration, global configuration should contain sqs section"))
- }
-
- // PARSE CONFIGURATION -------
- var pipeCfg Config
- var globalCfg GlobalCfg
-
- err := cfg.UnmarshalKey(configKey, &pipeCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- pipeCfg.InitDefault()
-
- err = cfg.UnmarshalKey(pluginName, &globalCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- globalCfg.InitDefault()
-
- // initialize job consumer
- jb := &consumer{
- pq: pq,
- log: log,
- eh: e,
- messageGroupID: uuid.NewString(),
- attributes: pipeCfg.Attributes,
- tags: pipeCfg.Tags,
- queue: pipeCfg.Queue,
- prefetch: pipeCfg.Prefetch,
- visibilityTimeout: pipeCfg.VisibilityTimeout,
- waitTime: pipeCfg.WaitTimeSeconds,
- region: globalCfg.Region,
- key: globalCfg.Key,
- sessionToken: globalCfg.SessionToken,
- secret: globalCfg.Secret,
- endpoint: globalCfg.Endpoint,
- pauseCh: make(chan struct{}, 1),
- }
-
- // PARSE CONFIGURATION -------
-
- awsConf, err := config.LoadDefaultConfig(context.Background(),
- config.WithRegion(globalCfg.Region),
- config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(jb.key, jb.secret, jb.sessionToken)))
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // config with retries
- jb.client = sqs.NewFromConfig(awsConf, sqs.WithEndpointResolver(sqs.EndpointResolverFromURL(jb.endpoint)), func(o *sqs.Options) {
- o.Retryer = retry.NewStandard(func(opts *retry.StandardOptions) {
- opts.MaxAttempts = 60
- })
- })
-
- out, err := jb.client.CreateQueue(context.Background(), &sqs.CreateQueueInput{QueueName: jb.queue, Attributes: jb.attributes, Tags: jb.tags})
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // assign a queue URL
- jb.queueURL = out.QueueUrl
-
- // To successfully create a new queue, you must provide a
- // queue name that adheres to the limits related to queues
- // (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/limits-queues.html)
- // and is unique within the scope of your queues. After you create a queue, you
- // must wait at least one second after the queue is created to be able to use the <------------
- // queue. To get the queue URL, use the GetQueueUrl action. GetQueueUrl require
- time.Sleep(time.Second * 2)
-
- return jb, nil
-}
-
-func FromPipeline(pipe *pipeline.Pipeline, log logger.Logger, cfg cfgPlugin.Configurer, e events.Handler, pq priorityqueue.Queue) (*consumer, error) {
- const op = errors.Op("new_sqs_consumer")
-
- // if no global section
- if !cfg.Has(pluginName) {
- return nil, errors.E(op, errors.Str("no global sqs configuration, global configuration should contain sqs section"))
- }
-
- // PARSE CONFIGURATION -------
- var globalCfg GlobalCfg
-
- err := cfg.UnmarshalKey(pluginName, &globalCfg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- globalCfg.InitDefault()
-
- attr := make(map[string]string)
- err = pipe.Map(attributes, attr)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- tg := make(map[string]string)
- err = pipe.Map(tags, tg)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // initialize job consumer
- jb := &consumer{
- pq: pq,
- log: log,
- eh: e,
- messageGroupID: uuid.NewString(),
- attributes: attr,
- tags: tg,
- queue: aws.String(pipe.String(queue, "default")),
- prefetch: int32(pipe.Int(pref, 10)),
- visibilityTimeout: int32(pipe.Int(visibility, 0)),
- waitTime: int32(pipe.Int(waitTime, 0)),
- region: globalCfg.Region,
- key: globalCfg.Key,
- sessionToken: globalCfg.SessionToken,
- secret: globalCfg.Secret,
- endpoint: globalCfg.Endpoint,
- pauseCh: make(chan struct{}, 1),
- }
-
- // PARSE CONFIGURATION -------
-
- awsConf, err := config.LoadDefaultConfig(context.Background(),
- config.WithRegion(globalCfg.Region),
- config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(jb.key, jb.secret, jb.sessionToken)))
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // config with retries
- jb.client = sqs.NewFromConfig(awsConf, sqs.WithEndpointResolver(sqs.EndpointResolverFromURL(jb.endpoint)), func(o *sqs.Options) {
- o.Retryer = retry.NewStandard(func(opts *retry.StandardOptions) {
- opts.MaxAttempts = 60
- })
- })
-
- out, err := jb.client.CreateQueue(context.Background(), &sqs.CreateQueueInput{QueueName: jb.queue, Attributes: jb.attributes, Tags: jb.tags})
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- // assign a queue URL
- jb.queueURL = out.QueueUrl
-
- // To successfully create a new queue, you must provide a
- // queue name that adheres to the limits related to queues
- // (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/limits-queues.html)
- // and is unique within the scope of your queues. After you create a queue, you
- // must wait at least one second after the queue is created to be able to use the <------------
- // queue. To get the queue URL, use the GetQueueUrl action. GetQueueUrl require
- time.Sleep(time.Second * 2)
-
- return jb, nil
-}
-
-func (c *consumer) Push(ctx context.Context, jb *job.Job) error {
- const op = errors.Op("sqs_push")
- // check if the pipeline registered
-
- // load atomic value
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != jb.Options.Pipeline {
- return errors.E(op, errors.Errorf("no such pipeline: %s, actual: %s", jb.Options.Pipeline, pipe.Name()))
- }
-
- // The length of time, in seconds, for which to delay a specific message. Valid
- // values: 0 to 900. Maximum: 15 minutes.
- if jb.Options.Delay > 900 {
- return errors.E(op, errors.Errorf("unable to push, maximum possible delay is 900 seconds (15 minutes), provided: %d", jb.Options.Delay))
- }
-
- err := c.handleItem(ctx, fromJob(jb))
- if err != nil {
- return errors.E(op, err)
- }
- return nil
-}
-
-func (c *consumer) State(ctx context.Context) (*jobState.State, error) {
- const op = errors.Op("sqs_state")
- attr, err := c.client.GetQueueAttributes(ctx, &sqs.GetQueueAttributesInput{
- QueueUrl: c.queueURL,
- AttributeNames: []types.QueueAttributeName{
- types.QueueAttributeNameApproximateNumberOfMessages,
- types.QueueAttributeNameApproximateNumberOfMessagesDelayed,
- types.QueueAttributeNameApproximateNumberOfMessagesNotVisible,
- },
- })
-
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
-
- out := &jobState.State{
- Pipeline: pipe.Name(),
- Driver: pipe.Driver(),
- Queue: *c.queueURL,
- Ready: ready(atomic.LoadUint32(&c.listeners)),
- }
-
- nom, err := strconv.Atoi(attr.Attributes[string(types.QueueAttributeNameApproximateNumberOfMessages)])
- if err == nil {
- out.Active = int64(nom)
- }
-
- delayed, err := strconv.Atoi(attr.Attributes[string(types.QueueAttributeNameApproximateNumberOfMessagesDelayed)])
- if err == nil {
- out.Delayed = int64(delayed)
- }
-
- nv, err := strconv.Atoi(attr.Attributes[string(types.QueueAttributeNameApproximateNumberOfMessagesNotVisible)])
- if err == nil {
- out.Reserved = int64(nv)
- }
-
- return out, nil
-}
-
-func (c *consumer) Register(_ context.Context, p *pipeline.Pipeline) error {
- c.pipeline.Store(p)
- return nil
-}
-
-func (c *consumer) Run(_ context.Context, p *pipeline.Pipeline) error {
- start := time.Now()
- const op = errors.Op("sqs_run")
-
- c.Lock()
- defer c.Unlock()
-
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p.Name() {
- return errors.E(op, errors.Errorf("no such pipeline registered: %s", pipe.Name()))
- }
-
- atomic.AddUint32(&c.listeners, 1)
-
- // start listener
- go c.listen(context.Background())
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-
- return nil
-}
-
-func (c *consumer) Stop(context.Context) error {
- start := time.Now()
- if atomic.LoadUint32(&c.listeners) > 0 {
- c.pauseCh <- struct{}{}
- }
-
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeStopped,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
- return nil
-}
-
-func (c *consumer) Pause(_ context.Context, p string) {
- start := time.Now()
- // load atomic value
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- c.log.Error("no such pipeline", "requested", p, "actual", pipe.Name())
- return
- }
-
- l := atomic.LoadUint32(&c.listeners)
- // no active listeners
- if l == 0 {
- c.log.Warn("no active listeners, nothing to pause")
- return
- }
-
- atomic.AddUint32(&c.listeners, ^uint32(0))
-
- // stop consume
- c.pauseCh <- struct{}{}
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipePaused,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (c *consumer) Resume(_ context.Context, p string) {
- start := time.Now()
- // load atomic value
- pipe := c.pipeline.Load().(*pipeline.Pipeline)
- if pipe.Name() != p {
- c.log.Error("no such pipeline", "requested", p, "actual", pipe.Name())
- return
- }
-
- l := atomic.LoadUint32(&c.listeners)
- // no active listeners
- if l == 1 {
- c.log.Warn("sqs listener already in the active state")
- return
- }
-
- // start listener
- go c.listen(context.Background())
-
- // increase num of listeners
- atomic.AddUint32(&c.listeners, 1)
-
- c.eh.Push(events.JobEvent{
- Event: events.EventPipeActive,
- Driver: pipe.Driver(),
- Pipeline: pipe.Name(),
- Start: start,
- Elapsed: time.Since(start),
- })
-}
-
-func (c *consumer) handleItem(ctx context.Context, msg *Item) error {
- d, err := msg.pack(c.queueURL)
- if err != nil {
- return err
- }
- _, err = c.client.SendMessage(ctx, d)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func ready(r uint32) bool {
- return r > 0
-}
diff --git a/plugins/sqs/item.go b/plugins/sqs/item.go
deleted file mode 100644
index 969d8b5b..00000000
--- a/plugins/sqs/item.go
+++ /dev/null
@@ -1,250 +0,0 @@
-package sqs
-
-import (
- "context"
- "strconv"
- "time"
-
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/service/sqs"
- "github.com/aws/aws-sdk-go-v2/service/sqs/types"
- json "github.com/json-iterator/go"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/jobs/job"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-const (
- StringType string = "String"
- NumberType string = "Number"
- BinaryType string = "Binary"
- ApproximateReceiveCount string = "ApproximateReceiveCount"
-)
-
-var itemAttributes = []string{
- job.RRID,
- job.RRJob,
- job.RRDelay,
- job.RRPriority,
- job.RRHeaders,
-}
-
-type Item struct {
- // Job contains pluginName of job broker (usually PHP class).
- Job string `json:"job"`
-
- // Ident is unique identifier of the job, should be provided from outside
- Ident string `json:"id"`
-
- // Payload is string data (usually JSON) passed to Job broker.
- Payload string `json:"payload"`
-
- // Headers with key-values pairs
- Headers map[string][]string `json:"headers"`
-
- // Options contains set of PipelineOptions specific to job execution. Can be empty.
- Options *Options `json:"options,omitempty"`
-}
-
-// Options carry information about how to handle given job.
-type Options struct {
- // Priority is job priority, default - 10
- // pointer to distinguish 0 as a priority and nil as priority not set
- Priority int64 `json:"priority"`
-
- // Pipeline manually specified pipeline.
- Pipeline string `json:"pipeline,omitempty"`
-
- // Delay defines time duration to delay execution for. Defaults to none.
- Delay int64 `json:"delay,omitempty"`
-
- // Private ================
- approxReceiveCount int64
- queue *string
- receiptHandler *string
- client *sqs.Client
- requeueFn func(context.Context, *Item) error
-}
-
-// DelayDuration returns delay duration in a form of time.Duration.
-func (o *Options) DelayDuration() time.Duration {
- return time.Second * time.Duration(o.Delay)
-}
-
-func (i *Item) ID() string {
- return i.Ident
-}
-
-func (i *Item) Priority() int64 {
- return i.Options.Priority
-}
-
-// Body packs job payload into binary payload.
-func (i *Item) Body() []byte {
- return utils.AsBytes(i.Payload)
-}
-
-// Context packs job context (job, id) into binary payload.
-// Not used in the sqs, MessageAttributes used instead
-func (i *Item) Context() ([]byte, error) {
- ctx, err := json.Marshal(
- struct {
- ID string `json:"id"`
- Job string `json:"job"`
- Headers map[string][]string `json:"headers"`
- Pipeline string `json:"pipeline"`
- }{ID: i.Ident, Job: i.Job, Headers: i.Headers, Pipeline: i.Options.Pipeline},
- )
-
- if err != nil {
- return nil, err
- }
-
- return ctx, nil
-}
-
-func (i *Item) Ack() error {
- _, err := i.Options.client.DeleteMessage(context.Background(), &sqs.DeleteMessageInput{
- QueueUrl: i.Options.queue,
- ReceiptHandle: i.Options.receiptHandler,
- })
-
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (i *Item) Nack() error {
- // requeue message
- err := i.Options.requeueFn(context.Background(), i)
- if err != nil {
- return err
- }
-
- _, err = i.Options.client.DeleteMessage(context.Background(), &sqs.DeleteMessageInput{
- QueueUrl: i.Options.queue,
- ReceiptHandle: i.Options.receiptHandler,
- })
-
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (i *Item) Requeue(headers map[string][]string, delay int64) error {
- // overwrite the delay
- i.Options.Delay = delay
- i.Headers = headers
-
- // requeue message
- err := i.Options.requeueFn(context.Background(), i)
- if err != nil {
- return err
- }
-
- // Delete job from the queue only after successful requeue
- _, err = i.Options.client.DeleteMessage(context.Background(), &sqs.DeleteMessageInput{
- QueueUrl: i.Options.queue,
- ReceiptHandle: i.Options.receiptHandler,
- })
-
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func fromJob(job *job.Job) *Item {
- return &Item{
- Job: job.Job,
- Ident: job.Ident,
- Payload: job.Payload,
- Headers: job.Headers,
- Options: &Options{
- Priority: job.Options.Priority,
- Pipeline: job.Options.Pipeline,
- Delay: job.Options.Delay,
- },
- }
-}
-
-func (i *Item) pack(queue *string) (*sqs.SendMessageInput, error) {
- // pack headers map
- data, err := json.Marshal(i.Headers)
- if err != nil {
- return nil, err
- }
-
- return &sqs.SendMessageInput{
- MessageBody: aws.String(i.Payload),
- QueueUrl: queue,
- DelaySeconds: int32(i.Options.Delay),
- MessageAttributes: map[string]types.MessageAttributeValue{
- job.RRID: {DataType: aws.String(StringType), BinaryValue: nil, BinaryListValues: nil, StringListValues: nil, StringValue: aws.String(i.Ident)},
- job.RRJob: {DataType: aws.String(StringType), BinaryValue: nil, BinaryListValues: nil, StringListValues: nil, StringValue: aws.String(i.Job)},
- job.RRDelay: {DataType: aws.String(StringType), BinaryValue: nil, BinaryListValues: nil, StringListValues: nil, StringValue: aws.String(strconv.Itoa(int(i.Options.Delay)))},
- job.RRHeaders: {DataType: aws.String(BinaryType), BinaryValue: data, BinaryListValues: nil, StringListValues: nil, StringValue: nil},
- job.RRPriority: {DataType: aws.String(NumberType), BinaryValue: nil, BinaryListValues: nil, StringListValues: nil, StringValue: aws.String(strconv.Itoa(int(i.Options.Priority)))},
- },
- }, nil
-}
-
-func (c *consumer) unpack(msg *types.Message) (*Item, error) {
- const op = errors.Op("sqs_unpack")
- // reserved
- if _, ok := msg.Attributes[ApproximateReceiveCount]; !ok {
- return nil, errors.E(op, errors.Str("failed to unpack the ApproximateReceiveCount attribute"))
- }
-
- for i := 0; i < len(itemAttributes); i++ {
- if _, ok := msg.MessageAttributes[itemAttributes[i]]; !ok {
- return nil, errors.E(op, errors.Errorf("missing queue attribute: %s", itemAttributes[i]))
- }
- }
-
- var h map[string][]string
- err := json.Unmarshal(msg.MessageAttributes[job.RRHeaders].BinaryValue, &h)
- if err != nil {
- return nil, err
- }
-
- delay, err := strconv.Atoi(*msg.MessageAttributes[job.RRDelay].StringValue)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- priority, err := strconv.Atoi(*msg.MessageAttributes[job.RRPriority].StringValue)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- recCount, err := strconv.Atoi(msg.Attributes[ApproximateReceiveCount])
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- item := &Item{
- Job: *msg.MessageAttributes[job.RRJob].StringValue,
- Ident: *msg.MessageAttributes[job.RRID].StringValue,
- Payload: *msg.Body,
- Headers: h,
- Options: &Options{
- Delay: int64(delay),
- Priority: int64(priority),
-
- // private
- approxReceiveCount: int64(recCount),
- client: c.client,
- queue: c.queueURL,
- receiptHandler: msg.ReceiptHandle,
- requeueFn: c.handleItem,
- },
- }
-
- return item, nil
-}
diff --git a/plugins/sqs/listener.go b/plugins/sqs/listener.go
deleted file mode 100644
index 215dd6a5..00000000
--- a/plugins/sqs/listener.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package sqs
-
-import (
- "context"
- "time"
-
- "github.com/aws/aws-sdk-go-v2/aws/transport/http"
- "github.com/aws/aws-sdk-go-v2/service/sqs"
- "github.com/aws/aws-sdk-go-v2/service/sqs/types"
- "github.com/aws/smithy-go"
-)
-
-const (
- // All - get all message attribute names
- All string = "All"
-
- // NonExistentQueue AWS error code
- NonExistentQueue string = "AWS.SimpleQueueService.NonExistentQueue"
-)
-
-func (c *consumer) listen(ctx context.Context) { //nolint:gocognit
- for {
- select {
- case <-c.pauseCh:
- c.log.Warn("sqs listener stopped")
- return
- default:
- message, err := c.client.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{
- QueueUrl: c.queueURL,
- MaxNumberOfMessages: c.prefetch,
- AttributeNames: []types.QueueAttributeName{types.QueueAttributeName(ApproximateReceiveCount)},
- MessageAttributeNames: []string{All},
- // The new value for the message's visibility timeout (in seconds). Values range: 0
- // to 43200. Maximum: 12 hours.
- VisibilityTimeout: c.visibilityTimeout,
- WaitTimeSeconds: c.waitTime,
- })
-
- if err != nil {
- if oErr, ok := (err).(*smithy.OperationError); ok {
- if rErr, ok := oErr.Err.(*http.ResponseError); ok {
- if apiErr, ok := rErr.Err.(*smithy.GenericAPIError); ok {
- // in case of NonExistentQueue - recreate the queue
- if apiErr.Code == NonExistentQueue {
- c.log.Error("receive message", "error code", apiErr.ErrorCode(), "message", apiErr.ErrorMessage(), "error fault", apiErr.ErrorFault())
- _, err = c.client.CreateQueue(context.Background(), &sqs.CreateQueueInput{QueueName: c.queue, Attributes: c.attributes, Tags: c.tags})
- if err != nil {
- c.log.Error("create queue", "error", err)
- }
- // To successfully create a new queue, you must provide a
- // queue name that adheres to the limits related to the queues
- // (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/limits-queues.html)
- // and is unique within the scope of your queues. After you create a queue, you
- // must wait at least one second after the queue is created to be able to use the <------------
- // queue. To get the queue URL, use the GetQueueUrl action. GetQueueUrl require
- time.Sleep(time.Second * 2)
- continue
- }
- }
- }
- }
-
- c.log.Error("receive message", "error", err)
- continue
- }
-
- for i := 0; i < len(message.Messages); i++ {
- m := message.Messages[i]
- item, err := c.unpack(&m)
- if err != nil {
- _, errD := c.client.DeleteMessage(context.Background(), &sqs.DeleteMessageInput{
- QueueUrl: c.queueURL,
- ReceiptHandle: m.ReceiptHandle,
- })
- if errD != nil {
- c.log.Error("message unpack, failed to delete the message from the queue", "error", err)
- }
-
- c.log.Error("message unpack", "error", err)
- continue
- }
-
- c.pq.Insert(item)
- }
- }
- }
-}
diff --git a/plugins/sqs/plugin.go b/plugins/sqs/plugin.go
deleted file mode 100644
index 54f61ff5..00000000
--- a/plugins/sqs/plugin.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package sqs
-
-import (
- "github.com/spiral/roadrunner/v2/common/jobs"
- "github.com/spiral/roadrunner/v2/pkg/events"
- priorityqueue "github.com/spiral/roadrunner/v2/pkg/priority_queue"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/jobs/pipeline"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const (
- pluginName string = "sqs"
-)
-
-type Plugin struct {
- log logger.Logger
- cfg config.Configurer
-}
-
-func (p *Plugin) Init(log logger.Logger, cfg config.Configurer) error {
- p.log = log
- p.cfg = cfg
- return nil
-}
-
-func (p *Plugin) Available() {}
-
-func (p *Plugin) Name() string {
- return pluginName
-}
-
-func (p *Plugin) JobsConstruct(configKey string, e events.Handler, pq priorityqueue.Queue) (jobs.Consumer, error) {
- return NewSQSConsumer(configKey, p.log, p.cfg, e, pq)
-}
-
-func (p *Plugin) FromPipeline(pipe *pipeline.Pipeline, e events.Handler, pq priorityqueue.Queue) (jobs.Consumer, error) {
- return FromPipeline(pipe, p.log, p.cfg, e, pq)
-}
diff --git a/plugins/static/config.go b/plugins/static/config.go
deleted file mode 100644
index c3f9c17d..00000000
--- a/plugins/static/config.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package static
-
-import (
- "os"
-
- "github.com/spiral/errors"
-)
-
-// Config describes file location and controls access to them.
-type Config struct {
- Static *struct {
- // Dir contains name of directory to control access to.
- // Default - "."
- Dir string
-
- // CalculateEtag can be true/false and used to calculate etag for the static
- CalculateEtag bool `mapstructure:"calculate_etag"`
-
- // Weak etag `W/`
- Weak bool
-
- // forbid specifies list of file extensions which are forbidden for access.
- // example: .php, .exe, .bat, .htaccess and etc.
- Forbid []string
-
- // Allow specifies list of file extensions which are allowed for access.
- // example: .php, .exe, .bat, .htaccess and etc.
- Allow []string
-
- // Request headers to add to every static.
- Request map[string]string
-
- // Response headers to add to every static.
- Response map[string]string
- }
-}
-
-// Valid returns nil if config is valid.
-func (c *Config) Valid() error {
- const op = errors.Op("static_plugin_valid")
- st, err := os.Stat(c.Static.Dir)
- if err != nil {
- if os.IsNotExist(err) {
- return errors.E(op, errors.Errorf("root directory '%s' does not exists", c.Static.Dir))
- }
-
- return err
- }
-
- if !st.IsDir() {
- return errors.E(op, errors.Errorf("invalid root directory '%s'", c.Static.Dir))
- }
-
- return nil
-}
diff --git a/plugins/static/etag.go b/plugins/static/etag.go
deleted file mode 100644
index 5ee0d2f3..00000000
--- a/plugins/static/etag.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package static
-
-import (
- "hash/crc32"
- "io"
- "net/http"
-
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-const etag string = "Etag"
-
-// weak Etag prefix
-var weakPrefix = []byte(`W/`)
-
-// CRC32 table
-var crc32q = crc32.MakeTable(0x48D90782)
-
-// SetEtag sets etag for the file
-func SetEtag(weak bool, f http.File, name string, w http.ResponseWriter) {
- // preallocate
- calculatedEtag := make([]byte, 0, 64)
-
- // write weak
- if weak {
- calculatedEtag = append(calculatedEtag, weakPrefix...)
- calculatedEtag = append(calculatedEtag, '"')
- calculatedEtag = appendUint(calculatedEtag, crc32.Checksum(utils.AsBytes(name), crc32q))
- calculatedEtag = append(calculatedEtag, '"')
-
- w.Header().Set(etag, utils.AsString(calculatedEtag))
- return
- }
-
- // read the file content
- body, err := io.ReadAll(f)
- if err != nil {
- return
- }
-
- // skip for 0 body
- if len(body) == 0 {
- return
- }
-
- calculatedEtag = append(calculatedEtag, '"')
- calculatedEtag = appendUint(calculatedEtag, uint32(len(body)))
- calculatedEtag = append(calculatedEtag, '-')
- calculatedEtag = appendUint(calculatedEtag, crc32.Checksum(body, crc32q))
- calculatedEtag = append(calculatedEtag, '"')
-
- w.Header().Set(etag, utils.AsString(calculatedEtag))
-}
-
-// appendUint appends n to dst and returns the extended dst.
-func appendUint(dst []byte, n uint32) []byte {
- var b [20]byte
- buf := b[:]
- i := len(buf)
- var q uint32
- for n >= 10 {
- i--
- q = n / 10
- buf[i] = '0' + byte(n-q*10)
- n = q
- }
- i--
- buf[i] = '0' + byte(n)
-
- dst = append(dst, buf[i:]...)
- return dst
-}
diff --git a/plugins/static/plugin.go b/plugins/static/plugin.go
deleted file mode 100644
index f6d9a0f2..00000000
--- a/plugins/static/plugin.go
+++ /dev/null
@@ -1,188 +0,0 @@
-package static
-
-import (
- "net/http"
- "path"
- "strings"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-// PluginName contains default service name.
-const PluginName = "static"
-
-const RootPluginName = "http"
-
-// Plugin serves static files. Potentially convert into middleware?
-type Plugin struct {
- // server configuration (location, forbidden files and etc)
- cfg *Config
-
- log logger.Logger
-
- // root is initiated http directory
- root http.Dir
-
- // file extensions which are allowed to be served
- allowedExtensions map[string]struct{}
-
- // file extensions which are forbidden to be served
- forbiddenExtensions map[string]struct{}
-}
-
-// Init must return configure service and return true if service hasStatus enabled. Must return error in case of
-// misconfiguration. Services must not be used without proper configuration pushed first.
-func (s *Plugin) Init(cfg config.Configurer, log logger.Logger) error {
- const op = errors.Op("static_plugin_init")
- if !cfg.Has(RootPluginName) {
- return errors.E(op, errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(RootPluginName, &s.cfg)
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
-
- if s.cfg.Static == nil {
- return errors.E(op, errors.Disabled)
- }
-
- s.log = log
- s.root = http.Dir(s.cfg.Static.Dir)
-
- err = s.cfg.Valid()
- if err != nil {
- return errors.E(op, err)
- }
-
- // create 2 hashmaps with the allowed and forbidden file extensions
- s.allowedExtensions = make(map[string]struct{}, len(s.cfg.Static.Allow))
- s.forbiddenExtensions = make(map[string]struct{}, len(s.cfg.Static.Forbid))
-
- // init forbidden
- for i := 0; i < len(s.cfg.Static.Forbid); i++ {
- // skip empty lines
- if s.cfg.Static.Forbid[i] == "" {
- continue
- }
- s.forbiddenExtensions[s.cfg.Static.Forbid[i]] = struct{}{}
- }
-
- // init allowed
- for i := 0; i < len(s.cfg.Static.Allow); i++ {
- // skip empty lines
- if s.cfg.Static.Allow[i] == "" {
- continue
- }
- s.allowedExtensions[s.cfg.Static.Allow[i]] = struct{}{}
- }
-
- // check if any forbidden items presented in the allowed
- // if presented, delete such items from allowed
- for k := range s.forbiddenExtensions {
- delete(s.allowedExtensions, k)
- }
-
- // at this point we have distinct allowed and forbidden hashmaps, also with alwaysServed
- return nil
-}
-
-func (s *Plugin) Name() string {
- return PluginName
-}
-
-// Middleware must return true if request/response pair is handled within the middleware.
-func (s *Plugin) Middleware(next http.Handler) http.Handler {
- // Define the http.HandlerFunc
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // do not allow paths like ../../resource
- // only specified folder and resources in it
- // https://lgtm.com/rules/1510366186013/
- if strings.Contains(r.URL.Path, "..") {
- w.WriteHeader(http.StatusForbidden)
- return
- }
-
- if s.cfg.Static.Request != nil {
- for k, v := range s.cfg.Static.Request {
- r.Header.Add(k, v)
- }
- }
-
- if s.cfg.Static.Response != nil {
- for k, v := range s.cfg.Static.Response {
- w.Header().Set(k, v)
- }
- }
-
- // first - create a proper file path
- fPath := path.Clean(r.URL.Path)
- ext := strings.ToLower(path.Ext(fPath))
-
- // check that file extension in the forbidden list
- if _, ok := s.forbiddenExtensions[ext]; ok {
- s.log.Debug("file extension is forbidden", "ext", ext)
- next.ServeHTTP(w, r)
- return
- }
-
- // if we have some allowed extensions, we should check them
- // if not - all extensions allowed except forbidden
- if len(s.allowedExtensions) > 0 {
- // not found in allowed
- if _, ok := s.allowedExtensions[ext]; !ok {
- next.ServeHTTP(w, r)
- return
- }
-
- // file extension allowed
- }
-
- // ok, file is not in the forbidden list
- // Stat it and get file info
- f, err := s.root.Open(fPath)
- if err != nil {
- // else no such file, show error in logs only in debug mode
- s.log.Debug("no such file or directory", "error", err)
- // pass request to the worker
- next.ServeHTTP(w, r)
- return
- }
-
- // at high confidence there is should not be an error
- // because we stat-ed the path previously and know, that that is file (not a dir), and it exists
- finfo, err := f.Stat()
- if err != nil {
- // else no such file, show error in logs only in debug mode
- s.log.Debug("no such file or directory", "error", err)
- // pass request to the worker
- next.ServeHTTP(w, r)
- return
- }
-
- defer func() {
- err = f.Close()
- if err != nil {
- s.log.Error("file close error", "error", err)
- }
- }()
-
- // if provided path to the dir, do not serve the dir, but pass the request to the worker
- if finfo.IsDir() {
- s.log.Debug("possible path to dir provided")
- // pass request to the worker
- next.ServeHTTP(w, r)
- return
- }
-
- // set etag
- if s.cfg.Static.CalculateEtag {
- SetEtag(s.cfg.Static.Weak, f, finfo.Name(), w)
- }
-
- // we passed all checks - serve the file
- http.ServeContent(w, r, finfo.Name(), finfo.ModTime(), f)
- })
-}
diff --git a/plugins/status/config.go b/plugins/status/config.go
deleted file mode 100644
index f751898b..00000000
--- a/plugins/status/config.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package status
-
-import "net/http"
-
-// Config is the configuration reference for the Status plugin
-type Config struct {
- // Address of the http server
- Address string
- // Status code returned in case of fail, 503 by default
- UnavailableStatusCode int `mapstructure:"unavailable_status_code"`
-}
-
-// InitDefaults configuration options
-func (c *Config) InitDefaults() {
- if c.UnavailableStatusCode == 0 {
- c.UnavailableStatusCode = http.StatusServiceUnavailable
- }
-}
diff --git a/plugins/status/interface.go b/plugins/status/interface.go
deleted file mode 100644
index 9d5a13af..00000000
--- a/plugins/status/interface.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package status
-
-// Status consists of status code from the service
-type Status struct {
- Code int
-}
-
-// Checker interface used to get latest status from plugin
-type Checker interface {
- Status() Status
-}
-
-// Readiness interface used to get readiness status from the plugin
-// that means, that worker poll inside the plugin has 1+ plugins which are ready to work
-// at the particular moment
-type Readiness interface {
- Ready() Status
-}
diff --git a/plugins/status/plugin.go b/plugins/status/plugin.go
deleted file mode 100644
index b76ad0a3..00000000
--- a/plugins/status/plugin.go
+++ /dev/null
@@ -1,214 +0,0 @@
-package status
-
-import (
- "fmt"
- "net/http"
- "time"
-
- "github.com/gofiber/fiber/v2"
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-const (
- // PluginName declares public plugin name.
- PluginName = "status"
-)
-
-type Plugin struct {
- // plugins which needs to be checked just as Status
- statusRegistry map[string]Checker
- // plugins which needs to send Readiness status
- readyRegistry map[string]Readiness
- server *fiber.App
- log logger.Logger
- cfg *Config
-}
-
-func (c *Plugin) Init(log logger.Logger, cfg config.Configurer) error {
- const op = errors.Op("checker_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
- err := cfg.UnmarshalKey(PluginName, &c.cfg)
- if err != nil {
- return errors.E(op, errors.Disabled, err)
- }
-
- // init defaults for the status plugin
- c.cfg.InitDefaults()
-
- c.readyRegistry = make(map[string]Readiness)
- c.statusRegistry = make(map[string]Checker)
-
- c.log = log
-
- return nil
-}
-
-func (c *Plugin) Serve() chan error {
- errCh := make(chan error, 1)
- c.server = fiber.New(fiber.Config{
- ReadTimeout: time.Second * 5,
- WriteTimeout: time.Second * 5,
- IdleTimeout: time.Second * 5,
- DisableStartupMessage: true,
- })
-
- c.server.Use("/health", c.healthHandler)
- c.server.Use("/ready", c.readinessHandler)
-
- go func() {
- err := c.server.Listen(c.cfg.Address)
- if err != nil {
- errCh <- err
- }
- }()
-
- return errCh
-}
-
-func (c *Plugin) Stop() error {
- const op = errors.Op("checker_plugin_stop")
- err := c.server.Shutdown()
- if err != nil {
- return errors.E(op, err)
- }
- return nil
-}
-
-// status returns a Checker interface implementation
-// Reset named service. This is not an Status interface implementation
-func (c *Plugin) status(name string) (Status, error) {
- const op = errors.Op("checker_plugin_status")
- svc, ok := c.statusRegistry[name]
- if !ok {
- return Status{}, errors.E(op, errors.Errorf("no such plugin: %s", name))
- }
-
- return svc.Status(), nil
-}
-
-// ready used to provide a readiness check for the plugin
-func (c *Plugin) ready(name string) (Status, error) {
- const op = errors.Op("checker_plugin_ready")
- svc, ok := c.readyRegistry[name]
- if !ok {
- return Status{}, errors.E(op, errors.Errorf("no such plugin: %s", name))
- }
-
- return svc.Ready(), nil
-}
-
-// CollectCheckerImpls collects services which can provide Status.
-func (c *Plugin) CollectCheckerImpls(name endure.Named, r Checker) error {
- c.statusRegistry[name.Name()] = r
- return nil
-}
-
-// CollectReadinessImpls collects services which can provide Readiness check.
-func (c *Plugin) CollectReadinessImpls(name endure.Named, r Readiness) error {
- c.readyRegistry[name.Name()] = r
- return nil
-}
-
-// Collects declares services to be collected.
-func (c *Plugin) Collects() []interface{} {
- return []interface{}{
- c.CollectReadinessImpls,
- c.CollectCheckerImpls,
- }
-}
-
-// Name of the service.
-func (c *Plugin) Name() string {
- return PluginName
-}
-
-// Available interface implementation
-func (c *Plugin) Available() {}
-
-// RPC returns associated rpc service.
-func (c *Plugin) RPC() interface{} {
- return &rpc{srv: c, log: c.log}
-}
-
-type Plugins struct {
- Plugins []string `query:"plugin"`
-}
-
-const template string = "Service: %s: Status: %d\n"
-
-func (c *Plugin) healthHandler(ctx *fiber.Ctx) error {
- const op = errors.Op("checker_plugin_health_handler")
- plugins := &Plugins{}
- err := ctx.QueryParser(plugins)
- if err != nil {
- return errors.E(op, err)
- }
-
- if len(plugins.Plugins) == 0 {
- ctx.Status(http.StatusOK)
- _, _ = ctx.WriteString("No plugins provided in query. Query should be in form of: health?plugin=plugin1&plugin=plugin2 \n")
- return nil
- }
-
- // iterate over all provided plugins
- for i := 0; i < len(plugins.Plugins); i++ {
- // check if the plugin exists
- if plugin, ok := c.statusRegistry[plugins.Plugins[i]]; ok {
- st := plugin.Status()
- if st.Code >= 500 {
- // if there is 500 or 503 status code return immediately
- ctx.Status(c.cfg.UnavailableStatusCode)
- return nil
- } else if st.Code >= 100 && st.Code <= 400 {
- _, _ = ctx.WriteString(fmt.Sprintf(template, plugins.Plugins[i], st.Code))
- }
- } else {
- _, _ = ctx.WriteString(fmt.Sprintf("Service: %s not found", plugins.Plugins[i]))
- }
- }
-
- ctx.Status(http.StatusOK)
- return nil
-}
-
-// readinessHandler return 200OK if all plugins are ready to serve
-// if one of the plugins return status from the 5xx range, the status for all query will be 503
-func (c *Plugin) readinessHandler(ctx *fiber.Ctx) error {
- const op = errors.Op("checker_plugin_readiness_handler")
- plugins := &Plugins{}
- err := ctx.QueryParser(plugins)
- if err != nil {
- return errors.E(op, err)
- }
-
- if len(plugins.Plugins) == 0 {
- ctx.Status(http.StatusOK)
- _, _ = ctx.WriteString("No plugins provided in query. Query should be in form of: ready?plugin=plugin1&plugin=plugin2 \n")
- return nil
- }
-
- // iterate over all provided plugins
- for i := 0; i < len(plugins.Plugins); i++ {
- // check if the plugin exists
- if plugin, ok := c.readyRegistry[plugins.Plugins[i]]; ok {
- st := plugin.Ready()
- if st.Code >= 500 {
- // if there is 500 or 503 status code return immediately
- ctx.Status(c.cfg.UnavailableStatusCode)
- return nil
- } else if st.Code >= 100 && st.Code <= 400 {
- _, _ = ctx.WriteString(fmt.Sprintf(template, plugins.Plugins[i], st.Code))
- }
- } else {
- _, _ = ctx.WriteString(fmt.Sprintf("Service: %s not found", plugins.Plugins[i]))
- }
- }
-
- ctx.Status(http.StatusOK)
- return nil
-}
diff --git a/plugins/status/rpc.go b/plugins/status/rpc.go
deleted file mode 100644
index 755a06fa..00000000
--- a/plugins/status/rpc.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package status
-
-import (
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-type rpc struct {
- srv *Plugin
- log logger.Logger
-}
-
-// Status return current status of the provided plugin
-func (rpc *rpc) Status(service string, status *Status) error {
- const op = errors.Op("checker_rpc_status")
- rpc.log.Debug("started Status method", "service", service)
- st, err := rpc.srv.status(service)
- if err != nil {
- return errors.E(op, err)
- }
-
- *status = st
-
- rpc.log.Debug("status code", "code", st.Code)
- rpc.log.Debug("successfully finished the Status method")
- return nil
-}
-
-// Status return current status of the provided plugin
-func (rpc *rpc) Ready(service string, status *Status) error {
- const op = errors.Op("checker_rpc_ready")
- rpc.log.Debug("started Ready method", "service", service)
- st, err := rpc.srv.ready(service)
- if err != nil {
- return errors.E(op, err)
- }
-
- *status = st
-
- rpc.log.Debug("status code", "code", st.Code)
- rpc.log.Debug("successfully finished the Ready method")
- return nil
-}
diff --git a/plugins/websockets/commands/enums.go b/plugins/websockets/commands/enums.go
deleted file mode 100644
index 18c63be3..00000000
--- a/plugins/websockets/commands/enums.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package commands
-
-type Command string
-
-const (
- Leave string = "leave"
- Join string = "join"
- Headers string = "headers"
-)
diff --git a/plugins/websockets/config.go b/plugins/websockets/config.go
deleted file mode 100644
index 933a12e0..00000000
--- a/plugins/websockets/config.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package websockets
-
-import (
- "strings"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/pool"
-)
-
-/*
-websockets:
- broker: default
- allowed_origin: "*"
- path: "/ws"
-*/
-
-// Config represents configuration for the ws plugin
-type Config struct {
- // http path for the websocket
- Path string `mapstructure:"path"`
- AllowedOrigin string `mapstructure:"allowed_origin"`
- Broker string `mapstructure:"broker"`
-
- // wildcard origin
- allowedWOrigins []wildcard
- allowedOrigins []string
- allowedAll bool
-
- // Pool with the workers for the websockets
- Pool *pool.Config `mapstructure:"pool"`
-}
-
-// InitDefault initialize default values for the ws config
-func (c *Config) InitDefault() error {
- if c.Path == "" {
- c.Path = "/ws"
- }
-
- // broker is mandatory
- if c.Broker == "" {
- return errors.Str("broker key should be specified")
- }
-
- if c.Pool == nil {
- c.Pool = &pool.Config{}
- if c.Pool.NumWorkers == 0 {
- // 2 workers by default
- c.Pool.NumWorkers = 2
- }
-
- if c.Pool.AllocateTimeout == 0 {
- c.Pool.AllocateTimeout = time.Minute
- }
-
- if c.Pool.DestroyTimeout == 0 {
- c.Pool.DestroyTimeout = time.Minute
- }
- if c.Pool.Supervisor != nil {
- c.Pool.Supervisor.InitDefaults()
- }
- }
-
- if c.AllowedOrigin == "" {
- c.AllowedOrigin = "*"
- }
-
- // Normalize
- origin := strings.ToLower(c.AllowedOrigin)
- if origin == "*" {
- // If "*" is present in the list, turn the whole list into a match all
- c.allowedAll = true
- return nil
- } else if i := strings.IndexByte(origin, '*'); i >= 0 {
- // Split the origin in two: start and end string without the *
- w := wildcard{origin[0:i], origin[i+1:]}
- c.allowedWOrigins = append(c.allowedWOrigins, w)
- } else {
- c.allowedOrigins = append(c.allowedOrigins, origin)
- }
-
- return nil
-}
diff --git a/plugins/websockets/connection/connection.go b/plugins/websockets/connection/connection.go
deleted file mode 100644
index 04c29d83..00000000
--- a/plugins/websockets/connection/connection.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package connection
-
-import (
- "sync"
-
- "github.com/fasthttp/websocket"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/logger"
-)
-
-// Connection represents wrapped and safe to use from the different threads websocket connection
-type Connection struct {
- sync.RWMutex
- log logger.Logger
- conn *websocket.Conn
-}
-
-func NewConnection(wsConn *websocket.Conn, log logger.Logger) *Connection {
- return &Connection{
- conn: wsConn,
- log: log,
- }
-}
-
-func (c *Connection) Write(data []byte) error {
- c.Lock()
- defer c.Unlock()
-
- const op = errors.Op("websocket_write")
- // handle a case when a goroutine tried to write into the closed connection
- defer func() {
- if r := recover(); r != nil {
- c.log.Warn("panic handled, tried to write into the closed connection")
- }
- }()
-
- err := c.conn.WriteMessage(websocket.TextMessage, data)
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
-
-func (c *Connection) Read() (int, []byte, error) {
- const op = errors.Op("websocket_read")
-
- mt, data, err := c.conn.ReadMessage()
- if err != nil {
- return -1, nil, errors.E(op, err)
- }
-
- return mt, data, nil
-}
-
-func (c *Connection) Close() error {
- c.Lock()
- defer c.Unlock()
- const op = errors.Op("websocket_close")
-
- err := c.conn.Close()
- if err != nil {
- return errors.E(op, err)
- }
-
- return nil
-}
diff --git a/plugins/websockets/doc/broadcast.drawio b/plugins/websockets/doc/broadcast.drawio
deleted file mode 100644
index 230870f2..00000000
--- a/plugins/websockets/doc/broadcast.drawio
+++ /dev/null
@@ -1 +0,0 @@
-<mxfile host="Electron" modified="2021-05-27T20:56:56.848Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.5.1 Chrome/89.0.4389.128 Electron/12.0.9 Safari/537.36" etag="Pt0MY_-SPz7R7foQA1VL" version="14.5.1" type="device"><diagram id="fD2kwGC0DAS2S_q_IsmE" name="Page-1">7V1Zc9rIFv411HUeULV28WgwTjxjx06wJ8l9mRKoAU2ExEjCS3797W4t9IbAoMZLrqcqg1oLrT77OV8fOuZg8fgx9ZfzqySAUccAwWPHPOsYhmECC/0PjzwVI7ppesXILA2Dcmw9MAp/wXIQlKOrMIAZc2GeJFEeLtnBSRLHcJIzY36aJg/sZdMkYr916c+gMDCa+JE4+i0M8nkxagIA1ic+wXA2z/kzC7+6uhzI5n6QPFBD5rBjDtIkyYtPi8cBjPDyVQtT3He+4Ww9sxTG+S43PFlXl7c/+n48uTWjn1/SEMSn3V45t/ypemMYoAUoD5M0nyezJPaj4Xq0vx69TJIlukxHg//APH8q6eev8gQNzfNFVJ6Fj2H+nfr8A30GmmGXh2eYYUB18FQdxHn69J0+oG/Dx+v7yFF1o7gy5WJlySqdwIblqFjMT2cwb7qupCleLOobyoX/CJMFRBNCF6Qw8vPwnuUmv2TKWX1dfetNEqI5G6CUIN2y7eKeSoB0y2AfUsy1vG9NfvSBmsh6iDCFnEGuvFUc/53d9od/zbI/xr9On7y7rm7swSFpsooDGJTkOIBfKB75QZ3axi8Ui/xgOETOLxlaw/wUaws0ECcxrMbOQ7xYFE9x9FbLZNaBLMWwwrPp7hQPvvejVflVlpQTLv0xUvoM9fwonMXo8wQtDUzRwD1M8xAp1dPyxCIMgoJRYBb+8sfkeXiVl5iNyavY/Y59Vq87fgB8ZN641PjlzWs1S1OkgaPFdS0f3wUacD2PkbqKZPtKc/WYHntHMp1mUInUms7LSq1LSy3YUWpZLe9u0/IvL7XSpTeclxTbyjpQYvsV+gEa+Tz8fnvyATsmMMuw14O8oTRZ4Fea44PlapytxnhOMb48g3GQ4W/LictVX/aQpD9his8skR/2PjSCuYFk5eOBpjuGyyoE4zCFUOka1+H0jHssDWHrAqPIdcZ7oybS77Zlm+y6m62Qk+UR3VNAyybVRJHy27A/uh78ObwdofFPd2iVwclo+PWvIfpwfvd58EEg9cM8zOFo6RMd+ICiOZbkU6RTB0mUpORqM/ChN50QhZsmPyF1xpl4cDzFdyRxTo0D8tfknAs8sJGEumFxXrFhlwL5sA7SdKvkmDkVnzlAlb/0igzuzm7ys7zkFm2psaMt1e2XtKWiBzzy7+FaNYVJXJvLB58Yyili98pSjtPEDyZ+lq9tbvY+NGyzdALNNAxWPtsyl6brauyTLbsaOILJNAWG8H4HgiKLaRkeR9EDI6Ly0Y6hWazVdLRqmdUT1Ph/buOlNPvB+bKDNLttCZIsT4S+OUk2mpMbiHt6wFChm3X2qci3UJHsaOI52v3Fbu8VocMDWjGBtM9zdqc2/k/q7JI/0dkt/tpydh1W9TpeyQy0rwskvq6lzNcFwooeUW/uWUOgEkq1ut1Vb04iP8vCCac69XZVpyWqzs3R3qGVBvRm/hN1QambNos3qLiuruSVeuR89ztc0HyHaTffgT4U8243V/rCXsDuTsAutnxvR+GtcrKYhelxXGT1elqP+uMChg0FtOdKSM/hmbfXzO7CPJ99g+5tESiDF0HuDjUCVUkxZZDt9+Fdmc3eFQqUXNdhS0ftZBaRyXW0qgBXh2A69xx1LpboL38dnl2M8PcY4OJz92p4df31x6F+1hQ6E2lSMXB7Y5I8VJhU7AFOVoyeJKdoKPKzTOvvs+ki/1N/7D1G/t/9weWPv8r0F7Ps5h626hDrxPha7o6+ls7Yne2VvP0tTeWJ7mZqBPPWVPKxqpCw4gf7MEk+QhlA1Ltfb9CjQP9udKBoQj2woSsTzZ7jmr4kBGo13+/slO+XyWYb+f7B2Y9o8V83eFwtT/8MgssvZ3nW3afAvo8g7pmsf369ZichlC7FrngWY0cZbD0HJJ21JwjLoMQQMvXuj9d4ynGwLMX4hCmaI09iGs7EatrRvBuaXk18urMX1OTcOI7L6sS28vo9U+PTy0dLAleahGIE9+Xouae3upn0DSbO0S2dXXWrFXLqntbj4BK6oTmGAoo26SOKoAI9/WxZoIWn4SOO4mmiiqQRyBwuCGyY2LxSdevGevwsXMzQzKNwjP71f61SiF9yBmOY+mj2532MS4aplt3PWjKQllAYs3UxS+jVF9Em0lXmvu6TWGktlbIr6kx7Vlpwfytp7mgl24cWH0REQ3Qq5WQ9urlr5LnD7R3QDNNWUvTk4gqkGlWUSpqYkCLmzV3/8mL0SSDpM+MDc+IBIPMv+6Bny0L3KflrKz5wdI2DHCDbtqsCNJUpQNES7QMTeuUqcfcK8z5q0xbVZhNn7x7gG5xog31k+9llFBfwbFr6RRtzuNvuYHO4ivINTaShlcmnmwMViQ9swwikgeqZfq5ekfCJBssVtYg00dCGEpGuck9Y5S46vFvOUj8QwGgkcH1ADOhEaF79cYo+zfAnfE+JXitx3HCcJZOfmDOYR0hvxNJMrlssCrhbhBURFhY/COMZfu3iRWJ0STGwRryxMHPZ5ASeQeTK5a5EBKf5YY4EVpZZ6bO3xDWmy1Xoq2OKaUxHwjS2MssjMA2BRAyiEJZVrmeIaBtLZLlsWcSWgBg894i2WRSrii+XzOI4/67wBs3+AhmgELHaKToLlo/oX7IyoBjv5tgw43MWdQ6zcbfkXHyu9IOZ0wGcJKlPZI9cg8vKaRQiK1p/9VpMSsGpBq7qnRxIW64m+QpDXYpr0JqM+fvQ2JIfm6f8yF6vX4glPunhk/zMb5NlOCH3nBWTJTpi86xUzKGP7UkqTAKcpDAIkVIaEKW1SNKnD6KGuvGfooRsoynuTv0H9O/4KYdSdXbcNxvUavnkH6TuileJINH2AyJPyFKk2Qf5vDhdwGtWwti8+k3QVdOIOHxT4uQJmRB8fO4vwgjL+ycY3UP81Pb1isd7R6YHtCp5SKtfmc32epprK9IvkmKM6PrHAec3SzP/Haaatq2YJodJNVfu9nfPJVmNJnW71T2nSGY3GMxDM5Suw/t5vCku3lxAhUiSnTr/KMA9SvEO7X1KworAePvt53/WRs+2oXhyFK3I1ZsrWkfIzMm3egoKZhgHxAVAUkRunyRRVBa05tjEgQUFiS03rsDHZZLhwyJ+ID76PM+xnVpGK2yVXizZ1ypyp9GMdIGGTQQjx23VtnTd1mzu0dXxEbY4CEzy6fYWBeng5vLu48VngbisYtgSGAgk56vONvQCSxbMe8bYJMDpjZQWxHazDwAqW1uHXzvG7Mo2A7rCql8slhFcQJIcAjer8YhstQ7xuk3x8u4eAbchX9IY+CAiiGBGS0IEV0IEQxURJNlXtPBRmM07TIV/XAYGb0zNbRMKE2gAWJZnuLbnGIDbm6yO9y051vNV+yPPw9ao21Il52MJnFpN54n9UtoOJ/qeswWVLNywBfeMXOxe0w1qUMyWuP13UOdJL87eyVZea8MO67VjZFoWB/ppCSUCND5teiynyBbt8waswjukJ+C38raEUDcNjQuqu0drZqKLsdAn5GF0l8i7StIFCtqwoe+PbgmFk7RImj6E+bz2AnKcoszqsAgFmckk9HMYdOj6iEzuW3WZp94EyjHwY8/GSYp2XGabQzZ45ku7zJYcdPfKG2i4r9pxkNTOX2PLKhGJ+5GUJf1JvsJ4OfCQdZtKlOj//gLLWTzOSLK66MiRIkEkK1el7pG+Kr3+xTtR9u42ZW/x+7LbQTDZumZ5ki11VVMOXVPR+0huy8VCXmUNfgPymlzyua3N/JZmHY2Ckl1Ot3OMLOhm/pSkI/1s3l34S95k/4RP2GB3RQtdGnHyxOoSRokotuIO8Hu6dLuMceY6O6BYDrPurskGWK4n4hHqDsttQ4HlNBY97nUupm5I+MZzYLrt8sVI2TYl2cKrS4KJxrXYEzZeETnJc38yJ04u3eORtHeUZ//fHFEsfl+nqYtEMWT9M9QRRbRZBVEmBYyKqKsKn5VptaS8eVK4HCks3daALoqId0xqSBDulY8ZhPc8gCNb+nE1RjBVlZwgN/snMTZsVplFv617r57UeJRpCKOABmfQX0ENs5OReLwYyJJW9T4/Z76Okm5K5jekvo8OvzuIrTyhLUJPDGZNU8JSbYDL5NXGV1STV9Vk/8jBbHXh9p6RL9o23ZRkopB0Rljw/rjGBVes5y+Hp6QVawmolWDIRnf90eDrRR9ddX73mTp6D4GNubUajzxotkPvgZvvyycbKK5htzzyKkBhoCqGORv6aL03egJNdw122VtquNw1WC7Rdc1V0ROlUSPRAJzvw8Hd7fVXUZ7LJuwUKv6kEn7ck73A06sMSfWxr0NDFpIC4AxPzxWHpHzHZk9io6Vun7KEsyHW99RSwEdBhiFvI3he9spQmhSwPb7gptc/VvRiaX9DTAy8byo4EipIdioemQpiluB9U8GVUMF4cSrI9qS8Zyp4Eiq8eCHSfOFC5H5NoRXt2G0ldKtI+spDN0vcHF/UIetWP0yJge0DtMpyclhgDMZPHQZXIOWot+fmb61H2U6V8D7YsXc1dsOgfrxATXQMKyjpb0BBg2vx1E7BuKv3+Mi7q6R7pfy15T/T8KqxJVwPxVeWjdtdpR/6Cy77YVI9j+PiyofYhDE1gJBFfu4dHnuDIlSqmF8sjBSHkWHNE1VBp0zTMk3uw2AHhNvb1HXeNl3nuNxvj7Rju1yDZSTjaIZLjOJ0eSHiHRLTtjwFtOxSP2RR2a2jAWMqJfvbxIM9STwo20Z03HhQTPPyv4EJTj5eiz1A31x53uWQEj3ZPrqjolcsMSNyCWcwDkg3hE19F0ihXNJlgW/HQYrZG7ts0FV4XeuQXXs8YIm1sqT7EfCXy+fW8hXN2sCzptA+r2JSJp7UunFJhVngIUg1fAI36gynIT7PYh7C9X7lRTbrUEDfV/GeFssyslejO4+EcZdrPnLE+dt4rt9qwFGHArVgHOYa1LKJ8av9rJ/R15W/MYzCpYD1MSnPs/QDyHCFg1H3eg7LcstVNicVuDUevFP9rvF/KKX+ZQVXNXRmXDrME/QYGKicrYtnWzVqqh1z0qqGmi6BHq3bUqubjkemQ0cWqyVpPcYJ7EPJyKs4/Jcs293dxRmxCRn6hvXV+Gcy8XUFZIn8aGZNEOpxVMRykq/RwB9UvmoPv+rwEU6QaU1L3u8YjZ3MJK3TVMxMB3hq59SPi27EkjBzIhDPlIC+AkKx8xQpmfuaGufMC/VHt91qG5TCd8HGlFXlWIVcX18Wd90U0jfjOtMVSVd59Frv3eJfR+l7FOYV5qs0Ltmd4+FCjyicAbGlTXtlhDXJBWA9kTGFcyR28FvDXhyeYeVzeeO+NY98le23s3sy15rPe+/gW2MxTzAV1sEoCgXnV0mAg/7h/wA=</diagram></mxfile> \ No newline at end of file
diff --git a/plugins/websockets/doc/doc.go b/plugins/websockets/doc/doc.go
deleted file mode 100644
index fc214be8..00000000
--- a/plugins/websockets/doc/doc.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package doc
-
-/*
-RPC message structure:
-
-type Msg struct {
- // Topic message been pushed into.
- Topics_ []string `json:"topic"`
-
- // Command (join, leave, headers)
- Command_ string `json:"command"`
-
- // Broker (redis, memory)
- Broker_ string `json:"broker"`
-
- // Payload to be broadcasted
- Payload_ []byte `json:"payload"`
-}
-
-1. Topics - string array (slice) with topics to join or leave
-2. Command - string, command to apply on the provided topics
-3. Broker - string, pub-sub broker to use, for the one-node systems might be used `memory` broker or `redis`. For the multi-node -
-`redis` broker should be used.
-4. Payload - raw byte array to send to the subscribers (binary messages).
-
-
-*/
diff --git a/plugins/websockets/executor/executor.go b/plugins/websockets/executor/executor.go
deleted file mode 100644
index c1f79a78..00000000
--- a/plugins/websockets/executor/executor.go
+++ /dev/null
@@ -1,214 +0,0 @@
-package executor
-
-import (
- "fmt"
- "net/http"
- "sync"
-
- json "github.com/json-iterator/go"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/websockets/commands"
- "github.com/spiral/roadrunner/v2/plugins/websockets/connection"
- "github.com/spiral/roadrunner/v2/plugins/websockets/validator"
- websocketsv1 "github.com/spiral/roadrunner/v2/proto/websockets/v1beta"
-)
-
-type Response struct {
- Topic string `json:"topic"`
- Payload []string `json:"payload"`
-}
-
-type Executor struct {
- sync.Mutex
- // raw ws connection
- conn *connection.Connection
- log logger.Logger
-
- // associated connection ID
- connID string
-
- // subscriber drivers
- sub pubsub.Subscriber
- actualTopics map[string]struct{}
-
- req *http.Request
- accessValidator validator.AccessValidatorFn
-}
-
-// NewExecutor creates protected connection and starts command loop
-func NewExecutor(conn *connection.Connection, log logger.Logger,
- connID string, sub pubsub.Subscriber, av validator.AccessValidatorFn, r *http.Request) *Executor {
- return &Executor{
- conn: conn,
- connID: connID,
- log: log,
- sub: sub,
- accessValidator: av,
- actualTopics: make(map[string]struct{}, 10),
- req: r,
- }
-}
-
-func (e *Executor) StartCommandLoop() error { //nolint:gocognit
- const op = errors.Op("executor_command_loop")
- for {
- mt, data, err := e.conn.Read()
- if err != nil {
- if mt == -1 {
- e.log.Info("socket was closed", "reason", err, "message type", mt)
- return nil
- }
-
- return errors.E(op, err)
- }
-
- msg := &websocketsv1.Message{}
-
- err = json.Unmarshal(data, msg)
- if err != nil {
- e.log.Error("unmarshal message", "error", err)
- continue
- }
-
- // nil message, continue
- if msg == nil {
- e.log.Warn("nil message, skipping")
- continue
- }
-
- switch msg.Command {
- // handle leave
- case commands.Join:
- e.log.Debug("received join command", "msg", msg)
-
- val, err := e.accessValidator(e.req, msg.Topics...)
- if err != nil {
- if val != nil {
- e.log.Debug("validation error", "status", val.Status, "headers", val.Header, "body", val.Body)
- }
-
- resp := &Response{
- Topic: "#join",
- Payload: msg.Topics,
- }
-
- packet, errJ := json.Marshal(resp)
- if errJ != nil {
- e.log.Error("marshal the body", "error", errJ)
- return errors.E(op, fmt.Errorf("%v,%v", err, errJ))
- }
-
- errW := e.conn.Write(packet)
- if errW != nil {
- e.log.Error("write payload to the connection", "payload", packet, "error", errW)
- return errors.E(op, fmt.Errorf("%v,%v", err, errW))
- }
-
- continue
- }
-
- resp := &Response{
- Topic: "@join",
- Payload: msg.Topics,
- }
-
- packet, err := json.Marshal(resp)
- if err != nil {
- e.log.Error("marshal the body", "error", err)
- return errors.E(op, err)
- }
-
- err = e.conn.Write(packet)
- if err != nil {
- e.log.Error("write payload to the connection", "payload", packet, "error", err)
- return errors.E(op, err)
- }
-
- // subscribe to the topic
- err = e.Set(msg.Topics)
- if err != nil {
- return errors.E(op, err)
- }
-
- // handle leave
- case commands.Leave:
- e.log.Debug("received leave command", "msg", msg)
-
- // prepare response
- resp := &Response{
- Topic: "@leave",
- Payload: msg.Topics,
- }
-
- packet, err := json.Marshal(resp)
- if err != nil {
- e.log.Error("marshal the body", "error", err)
- return errors.E(op, err)
- }
-
- err = e.conn.Write(packet)
- if err != nil {
- e.log.Error("write payload to the connection", "payload", packet, "error", err)
- return errors.E(op, err)
- }
-
- err = e.Leave(msg.Topics)
- if err != nil {
- return errors.E(op, err)
- }
-
- case commands.Headers:
-
- default:
- e.log.Warn("unknown command", "command", msg.Command)
- }
- }
-}
-
-func (e *Executor) Set(topics []string) error {
- // associate connection with topics
- err := e.sub.Subscribe(e.connID, topics...)
- if err != nil {
- e.log.Error("subscribe to the provided topics", "topics", topics, "error", err.Error())
- // in case of error, unsubscribe connection from the dead topics
- _ = e.sub.Unsubscribe(e.connID, topics...)
- return err
- }
-
- // save topics for the connection
- for i := 0; i < len(topics); i++ {
- e.actualTopics[topics[i]] = struct{}{}
- }
-
- return nil
-}
-
-func (e *Executor) Leave(topics []string) error {
- // remove associated connections from the storage
- err := e.sub.Unsubscribe(e.connID, topics...)
- if err != nil {
- e.log.Error("subscribe to the provided topics", "topics", topics, "error", err.Error())
- return err
- }
-
- // remove topics for the connection
- for i := 0; i < len(topics); i++ {
- delete(e.actualTopics, topics[i])
- }
-
- return nil
-}
-
-func (e *Executor) CleanUp() {
- // unsubscribe particular connection from the topics
- for topic := range e.actualTopics {
- _ = e.sub.Unsubscribe(e.connID, topic)
- }
-
- // clean up the actualTopics data
- for k := range e.actualTopics {
- delete(e.actualTopics, k)
- }
-}
diff --git a/plugins/websockets/origin.go b/plugins/websockets/origin.go
deleted file mode 100644
index c6d9c9b8..00000000
--- a/plugins/websockets/origin.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package websockets
-
-import (
- "strings"
-)
-
-func isOriginAllowed(origin string, cfg *Config) bool {
- if cfg.allowedAll {
- return true
- }
-
- origin = strings.ToLower(origin)
- // simple case
- origin = strings.ToLower(origin)
- for _, o := range cfg.allowedOrigins {
- if o == origin {
- return true
- }
- }
- // check wildcards
- for _, w := range cfg.allowedWOrigins {
- if w.match(origin) {
- return true
- }
- }
-
- return false
-}
diff --git a/plugins/websockets/origin_test.go b/plugins/websockets/origin_test.go
deleted file mode 100644
index bbc49bbb..00000000
--- a/plugins/websockets/origin_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package websockets
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestConfig_Origin(t *testing.T) {
- cfg := &Config{
- AllowedOrigin: "*",
- Broker: "any",
- }
-
- err := cfg.InitDefault()
- assert.NoError(t, err)
-
- assert.True(t, isOriginAllowed("http://some.some.some.sssome", cfg))
- assert.True(t, isOriginAllowed("http://", cfg))
- assert.True(t, isOriginAllowed("http://google.com", cfg))
- assert.True(t, isOriginAllowed("ws://*", cfg))
- assert.True(t, isOriginAllowed("*", cfg))
- assert.True(t, isOriginAllowed("you are bad programmer", cfg)) // True :(
- assert.True(t, isOriginAllowed("****", cfg))
- assert.True(t, isOriginAllowed("asde!@#!!@#!%", cfg))
- assert.True(t, isOriginAllowed("http://*.domain.com", cfg))
-}
-
-func TestConfig_OriginWildCard(t *testing.T) {
- cfg := &Config{
- AllowedOrigin: "https://*my.site.com",
- Broker: "any",
- }
-
- err := cfg.InitDefault()
- assert.NoError(t, err)
-
- assert.True(t, isOriginAllowed("https://my.site.com", cfg))
- assert.False(t, isOriginAllowed("http://", cfg))
- assert.False(t, isOriginAllowed("http://google.com", cfg))
- assert.False(t, isOriginAllowed("ws://*", cfg))
- assert.False(t, isOriginAllowed("*", cfg))
- assert.False(t, isOriginAllowed("you are bad programmer", cfg)) // True :(
- assert.False(t, isOriginAllowed("****", cfg))
- assert.False(t, isOriginAllowed("asde!@#!!@#!%", cfg))
- assert.False(t, isOriginAllowed("http://*.domain.com", cfg))
-
- assert.False(t, isOriginAllowed("https://*site.com", cfg))
- assert.True(t, isOriginAllowed("https://some.my.site.com", cfg))
-}
-
-func TestConfig_OriginWildCard2(t *testing.T) {
- cfg := &Config{
- AllowedOrigin: "https://my.*.com",
- Broker: "any",
- }
-
- err := cfg.InitDefault()
- assert.NoError(t, err)
-
- assert.True(t, isOriginAllowed("https://my.site.com", cfg))
- assert.False(t, isOriginAllowed("http://", cfg))
- assert.False(t, isOriginAllowed("http://google.com", cfg))
- assert.False(t, isOriginAllowed("ws://*", cfg))
- assert.False(t, isOriginAllowed("*", cfg))
- assert.False(t, isOriginAllowed("you are bad programmer", cfg)) // True :(
- assert.False(t, isOriginAllowed("****", cfg))
- assert.False(t, isOriginAllowed("asde!@#!!@#!%", cfg))
- assert.False(t, isOriginAllowed("http://*.domain.com", cfg))
-
- assert.False(t, isOriginAllowed("https://*site.com", cfg))
- assert.True(t, isOriginAllowed("https://my.bad.com", cfg))
-}
diff --git a/plugins/websockets/plugin.go b/plugins/websockets/plugin.go
deleted file mode 100644
index 395b056f..00000000
--- a/plugins/websockets/plugin.go
+++ /dev/null
@@ -1,370 +0,0 @@
-package websockets
-
-import (
- "context"
- "net/http"
- "sync"
- "time"
-
- "github.com/fasthttp/websocket"
- "github.com/google/uuid"
- json "github.com/json-iterator/go"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/pkg/payload"
- phpPool "github.com/spiral/roadrunner/v2/pkg/pool"
- "github.com/spiral/roadrunner/v2/pkg/state/process"
- "github.com/spiral/roadrunner/v2/pkg/worker"
- "github.com/spiral/roadrunner/v2/plugins/broadcast"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/http/attributes"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/server"
- "github.com/spiral/roadrunner/v2/plugins/websockets/connection"
- "github.com/spiral/roadrunner/v2/plugins/websockets/executor"
- "github.com/spiral/roadrunner/v2/plugins/websockets/pool"
- "github.com/spiral/roadrunner/v2/plugins/websockets/validator"
-)
-
-const (
- PluginName string = "websockets"
-
- RrMode string = "RR_MODE"
- RrBroadcastPath string = "RR_BROADCAST_PATH"
-)
-
-type Plugin struct {
- sync.RWMutex
-
- // subscriber+reader interfaces
- subReader pubsub.SubReader
- // broadcaster
- broadcaster broadcast.Broadcaster
-
- cfg *Config
- log logger.Logger
-
- // global connections map
- connections sync.Map
-
- // GO workers pool
- workersPool *pool.WorkersPool
-
- wsUpgrade *websocket.Upgrader
- serveExit chan struct{}
-
- // workers pool
- phpPool phpPool.Pool
- // server which produces commands to the pool
- server server.Server
-
- // stop receiving messages
- cancel context.CancelFunc
- ctx context.Context
-
- // function used to validate access to the requested resource
- accessValidator validator.AccessValidatorFn
-}
-
-func (p *Plugin) Init(cfg config.Configurer, log logger.Logger, server server.Server, b broadcast.Broadcaster) error {
- const op = errors.Op("websockets_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(PluginName, &p.cfg)
- if err != nil {
- return errors.E(op, err)
- }
-
- err = p.cfg.InitDefault()
- if err != nil {
- return errors.E(op, err)
- }
-
- p.wsUpgrade = &websocket.Upgrader{
- HandshakeTimeout: time.Second * 60,
- ReadBufferSize: 1024,
- WriteBufferSize: 1024,
- CheckOrigin: func(r *http.Request) bool {
- return isOriginAllowed(r.Header.Get("Origin"), p.cfg)
- },
- }
- p.serveExit = make(chan struct{})
- p.server = server
- p.log = log
- p.broadcaster = b
-
- ctx, cancel := context.WithCancel(context.Background())
- p.ctx = ctx
- p.cancel = cancel
- return nil
-}
-
-func (p *Plugin) Serve() chan error {
- const op = errors.Op("websockets_plugin_serve")
- errCh := make(chan error, 1)
- // init broadcaster
- var err error
- p.subReader, err = p.broadcaster.GetDriver(p.cfg.Broker)
- if err != nil {
- errCh <- errors.E(op, err)
- return errCh
- }
-
- go func() {
- var err error
- p.Lock()
- defer p.Unlock()
-
- p.phpPool, err = p.server.NewWorkerPool(context.Background(), &phpPool.Config{
- Debug: p.cfg.Pool.Debug,
- NumWorkers: p.cfg.Pool.NumWorkers,
- MaxJobs: p.cfg.Pool.MaxJobs,
- AllocateTimeout: p.cfg.Pool.AllocateTimeout,
- DestroyTimeout: p.cfg.Pool.DestroyTimeout,
- Supervisor: p.cfg.Pool.Supervisor,
- }, map[string]string{RrMode: "http", RrBroadcastPath: p.cfg.Path})
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- p.accessValidator = p.defaultAccessValidator(p.phpPool)
- }()
-
- p.workersPool = pool.NewWorkersPool(p.subReader, &p.connections, p.log)
-
- // we need here only Reader part of the interface
- go func(ps pubsub.Reader) {
- for {
- data, err := ps.Next(p.ctx)
- if err != nil {
- if errors.Is(errors.TimeOut, err) {
- return
- }
-
- errCh <- errors.E(op, err)
- return
- }
-
- p.workersPool.Queue(data)
- }
- }(p.subReader)
-
- return errCh
-}
-
-func (p *Plugin) Stop() error {
- // close workers pool
- p.workersPool.Stop()
- // cancel context
- p.cancel()
- p.Lock()
- if p.phpPool == nil {
- p.Unlock()
- return nil
- }
- p.phpPool.Destroy(context.Background())
- p.Unlock()
-
- return nil
-}
-
-func (p *Plugin) Available() {}
-
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-func (p *Plugin) Middleware(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != p.cfg.Path {
- next.ServeHTTP(w, r)
- return
- }
-
- // we need to lock here, because accessValidator might not be set in the Serve func at the moment
- p.RLock()
- // before we hijacked connection, we still can write to the response headers
- val, err := p.accessValidator(r)
- p.RUnlock()
- if err != nil {
- p.log.Error("access validation")
- w.WriteHeader(400)
- return
- }
-
- if val.Status != http.StatusOK {
- for k, v := range val.Header {
- for i := 0; i < len(v); i++ {
- w.Header().Add(k, v[i])
- }
- }
- w.WriteHeader(val.Status)
- _, _ = w.Write(val.Body)
- return
- }
-
- // upgrade connection to websocket connection
- _conn, err := p.wsUpgrade.Upgrade(w, r, nil)
- if err != nil {
- // connection hijacked, do not use response.writer or request
- p.log.Error("upgrade connection", "error", err)
- return
- }
-
- // construct safe connection protected by mutexes
- safeConn := connection.NewConnection(_conn, p.log)
- // generate UUID from the connection
- connectionID := uuid.NewString()
- // store connection
- p.connections.Store(connectionID, safeConn)
-
- // Executor wraps a connection to have a safe abstraction
- e := executor.NewExecutor(safeConn, p.log, connectionID, p.subReader, p.accessValidator, r)
- p.log.Info("websocket client connected", "uuid", connectionID)
-
- err = e.StartCommandLoop()
- if err != nil {
- p.log.Error("command loop error, disconnecting", "error", err.Error())
- return
- }
-
- // when exiting - delete the connection
- p.connections.Delete(connectionID)
-
- // remove connection from all topics from all pub-sub drivers
- e.CleanUp()
-
- err = r.Body.Close()
- if err != nil {
- p.log.Error("body close", "error", err)
- }
-
- // close the connection on exit
- err = safeConn.Close()
- if err != nil {
- p.log.Error("connection close", "error", err)
- }
-
- safeConn = nil
- p.log.Info("disconnected", "connectionID", connectionID)
- })
-}
-
-// Workers returns slice with the process states for the workers
-func (p *Plugin) Workers() []*process.State {
- p.RLock()
- defer p.RUnlock()
-
- workers := p.workers()
-
- ps := make([]*process.State, 0, len(workers))
- for i := 0; i < len(workers); i++ {
- state, err := process.WorkerProcessState(workers[i])
- if err != nil {
- return nil
- }
- ps = append(ps, state)
- }
-
- return ps
-}
-
-// internal
-func (p *Plugin) workers() []worker.BaseProcess {
- return p.phpPool.Workers()
-}
-
-// Reset destroys the old pool and replaces it with new one, waiting for old pool to die
-func (p *Plugin) Reset() error {
- p.Lock()
- defer p.Unlock()
- const op = errors.Op("ws_plugin_reset")
- p.log.Info("WS plugin got restart request. Restarting...")
- p.phpPool.Destroy(context.Background())
- p.phpPool = nil
-
- var err error
- p.phpPool, err = p.server.NewWorkerPool(context.Background(), &phpPool.Config{
- Debug: p.cfg.Pool.Debug,
- NumWorkers: p.cfg.Pool.NumWorkers,
- MaxJobs: p.cfg.Pool.MaxJobs,
- AllocateTimeout: p.cfg.Pool.AllocateTimeout,
- DestroyTimeout: p.cfg.Pool.DestroyTimeout,
- Supervisor: p.cfg.Pool.Supervisor,
- }, map[string]string{RrMode: "http", RrBroadcastPath: p.cfg.Path})
- if err != nil {
- return errors.E(op, err)
- }
-
- // attach validators
- p.accessValidator = p.defaultAccessValidator(p.phpPool)
-
- p.log.Info("WS plugin successfully restarted")
- return nil
-}
-
-func (p *Plugin) defaultAccessValidator(pool phpPool.Pool) validator.AccessValidatorFn {
- return func(r *http.Request, topics ...string) (*validator.AccessValidator, error) {
- const op = errors.Op("access_validator")
-
- p.log.Debug("validation", "topics", topics)
- r = attributes.Init(r)
-
- // if channels len is eq to 0, we use serverValidator
- if len(topics) == 0 {
- ctx, err := validator.ServerAccessValidator(r)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- val, err := exec(ctx, pool)
- if err != nil {
- return nil, errors.E(err)
- }
-
- return val, nil
- }
-
- ctx, err := validator.TopicsAccessValidator(r, topics...)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- val, err := exec(ctx, pool)
- if err != nil {
- return nil, errors.E(op)
- }
-
- if val.Status != http.StatusOK {
- return val, errors.E(op, errors.Errorf("access forbidden, code: %d", val.Status))
- }
-
- return val, nil
- }
-}
-
-func exec(ctx []byte, pool phpPool.Pool) (*validator.AccessValidator, error) {
- const op = errors.Op("exec")
- pd := &payload.Payload{
- Context: ctx,
- }
-
- resp, err := pool.Exec(pd)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- val := &validator.AccessValidator{
- Body: resp.Body,
- }
-
- err = json.Unmarshal(resp.Context, val)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return val, nil
-}
diff --git a/plugins/websockets/pool/workers_pool.go b/plugins/websockets/pool/workers_pool.go
deleted file mode 100644
index 758620f6..00000000
--- a/plugins/websockets/pool/workers_pool.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package pool
-
-import (
- "sync"
-
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/v2/common/pubsub"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/websockets/connection"
- "github.com/spiral/roadrunner/v2/utils"
-)
-
-type WorkersPool struct {
- subscriber pubsub.Subscriber
- connections *sync.Map
- resPool sync.Pool
- log logger.Logger
-
- queue chan *pubsub.Message
- exit chan struct{}
-}
-
-// NewWorkersPool constructs worker pool for the websocket connections
-func NewWorkersPool(subscriber pubsub.Subscriber, connections *sync.Map, log logger.Logger) *WorkersPool {
- wp := &WorkersPool{
- connections: connections,
- queue: make(chan *pubsub.Message, 100),
- subscriber: subscriber,
- log: log,
- exit: make(chan struct{}),
- }
-
- wp.resPool.New = func() interface{} {
- return make(map[string]struct{}, 10)
- }
-
- // start 10 workers
- for i := 0; i < 50; i++ {
- wp.do()
- }
-
- return wp
-}
-
-func (wp *WorkersPool) Queue(msg *pubsub.Message) {
- wp.queue <- msg
-}
-
-func (wp *WorkersPool) Stop() {
- for i := 0; i < 50; i++ {
- wp.exit <- struct{}{}
- }
-
- close(wp.exit)
-}
-
-func (wp *WorkersPool) put(res map[string]struct{}) {
- // optimized
- // https://go-review.googlesource.com/c/go/+/110055/
- // not O(n), but O(1)
- for k := range res {
- delete(res, k)
- }
-}
-
-func (wp *WorkersPool) get() map[string]struct{} {
- return wp.resPool.Get().(map[string]struct{})
-}
-
-// Response from the server
-type Response struct {
- Topic string `json:"topic"`
- Payload string `json:"payload"`
-}
-
-func (wp *WorkersPool) do() { //nolint:gocognit
- go func() {
- for {
- select {
- case msg, ok := <-wp.queue:
- if !ok {
- return
- }
- _ = msg
- if msg == nil || msg.Topic == "" {
- continue
- }
-
- // get free map
- res := wp.get()
-
- // get connections for the particular topic
- wp.subscriber.Connections(msg.Topic, res)
-
- if len(res) == 0 {
- wp.log.Info("no connections associated with provided topic", "topic", msg.Topic)
- wp.put(res)
- continue
- }
-
- // res is a map with a connectionsID
- for connID := range res {
- c, ok := wp.connections.Load(connID)
- if !ok {
- wp.log.Warn("the websocket disconnected before the message being written to it", "topics", msg.Topic)
- wp.put(res)
- continue
- }
-
- d, err := json.Marshal(&Response{
- Topic: msg.Topic,
- Payload: utils.AsString(msg.Payload),
- })
-
- if err != nil {
- wp.log.Error("error marshaling response", "error", err)
- wp.put(res)
- break
- }
-
- // put data into the bytes buffer
- err = c.(*connection.Connection).Write(d)
- if err != nil {
- wp.log.Error("error sending payload over the connection", "error", err, "topic", msg.Topic)
- wp.put(res)
- continue
- }
- }
- case <-wp.exit:
- wp.log.Info("get exit signal, exiting from the workers pool")
- return
- }
- }
- }()
-}
diff --git a/plugins/websockets/validator/access_validator.go b/plugins/websockets/validator/access_validator.go
deleted file mode 100644
index 2685da7f..00000000
--- a/plugins/websockets/validator/access_validator.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package validator
-
-import (
- "net/http"
- "strings"
-
- json "github.com/json-iterator/go"
- "github.com/spiral/errors"
- handler "github.com/spiral/roadrunner/v2/pkg/worker_handler"
- "github.com/spiral/roadrunner/v2/plugins/http/attributes"
-)
-
-type AccessValidatorFn = func(r *http.Request, channels ...string) (*AccessValidator, error)
-
-const (
- joinServer string = "ws:joinServer"
- joinTopics string = "ws:joinTopics"
-)
-
-type AccessValidator struct {
- Header http.Header `json:"headers"`
- Status int `json:"status"`
- Body []byte
-}
-
-func ServerAccessValidator(r *http.Request) ([]byte, error) {
- const op = errors.Op("server_access_validator")
-
- err := attributes.Set(r, "ws:joinServer", true)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- defer delete(attributes.All(r), joinServer)
-
- req := &handler.Request{
- RemoteAddr: handler.FetchIP(r.RemoteAddr),
- Protocol: r.Proto,
- Method: r.Method,
- URI: handler.URI(r),
- Header: r.Header,
- Cookies: make(map[string]string),
- RawQuery: r.URL.RawQuery,
- Attributes: attributes.All(r),
- }
-
- data, err := json.Marshal(req)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return data, nil
-}
-
-func TopicsAccessValidator(r *http.Request, topics ...string) ([]byte, error) {
- const op = errors.Op("topic_access_validator")
- err := attributes.Set(r, "ws:joinTopics", strings.Join(topics, ","))
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- defer delete(attributes.All(r), joinTopics)
-
- req := &handler.Request{
- RemoteAddr: handler.FetchIP(r.RemoteAddr),
- Protocol: r.Proto,
- Method: r.Method,
- URI: handler.URI(r),
- Header: r.Header,
- Cookies: make(map[string]string),
- RawQuery: r.URL.RawQuery,
- Attributes: attributes.All(r),
- }
-
- data, err := json.Marshal(req)
- if err != nil {
- return nil, errors.E(op, err)
- }
-
- return data, nil
-}
diff --git a/plugins/websockets/wildcard.go b/plugins/websockets/wildcard.go
deleted file mode 100644
index 2f1c6601..00000000
--- a/plugins/websockets/wildcard.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package websockets
-
-import "strings"
-
-type wildcard struct {
- prefix string
- suffix string
-}
-
-func (w wildcard) match(s string) bool {
- return len(s) >= len(w.prefix)+len(w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix)
-}