Skip to content

[BUG] Redis pipeline() does not release connections back to pool in coroutines (connection pool exhausted error) #7351

@binaryfire

Description

@binaryfire

When Redis pipeline() is used inside coroutines, the Redis connection is not returned to the pool after the pipeline closure finishes. This is a problem for long-running coroutines.

To reproduce:

  1. Set the Redis config's pool.max_connections to 2.
  2. Add the controller below and call each endpoint 5-10 times. The direct endpoint will not cause connection pool exhaustion. The pipeline endpoint will cause a connection pool exception after 2 requests.
<?php

declare(strict_types=1);

namespace App\Controller;

use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use Hyperf\Redis\Redis;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\Coroutine\Coroutine;

/**
 * Demonstrates Redis connection pool exhaustion with pipelines
 *
 * Set Redis pool max_connections to 2 in config/autoload/redis.php,
 * then call each of the endpoints 5-10 times.
 */
#[Controller]
class RedisTestController
{
    public function __construct(
        protected Redis $redis,
        protected ResponseInterface $response,
    ) {}

    /**
     * Tests direct Redis commands in coroutines
     */
    #[RequestMapping(path: "/redis/direct", methods: ["GET"])]
    public function direct()
    {
        Coroutine::create(function () {
            $key1 = "direct:" . uniqid();
            $key2 = "direct::" . uniqid();

            // Each command releases the connection after use
            $this->redis->set($key1, "value-{$key1}");
            $this->redis->set($key2, "value-{$key2}");

            // Sleep to simulate work - connection is already released
            sleep(60);
        });

        return $this->response->json(['message' => 'success']);
    }

    /**
     * Tests pipeline Redis commands in coroutines
     */
    #[RequestMapping(path: "/redis/pipeline", methods: ["GET"])]
    public function pipeline()
    {
        Coroutine::create(function () {
            $key1 = "pipeline:" . uniqid();
            $key2 = "pipeline::" . uniqid();

            // Pipeline doesn't release connection properly
            $this->redis->pipeline(function ($pipe) use ($key1, $key2) {
                $pipe->set($key1, "value-{$key1}");
                $pipe->set($key2, "value-{$key2}");
            });

            // Sleep to simulate work - connection is still held!
            sleep(60);
        });


        return $this->response->json(['message' => 'success']);
    }
}

It would be great if pipeline() could be made coroutine-friendly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions