【问题标题】:Symfony Lock does not lock when making two requests from the same browser当从同一个浏览器发出两个请求时,Symfony Lock 不锁定
【发布时间】:2022-11-12 00:23:13
【问题描述】:

我想通过使用 Symfony Lock 组件来防止用户两次发出相同的请求。因为现在用户可以点击链接两次(意外?)并创建重复的实体。我想使用不能防止竞争条件本身的唯一实体约束。

Symfony Lock 组件似乎没有按预期工作。当我在页面的开头创建一个锁并同时打开该页面两次时,两个请求都可以获取锁。当我在标准和隐身浏览器窗口中打开测试页面时,第二个请求没有获得锁。但是我在文档中找不到任何关于此链接到会话的内容。我在一个新项目中创建了一个小测试文件来隔离问题。这是使用 php 7.4 symfony 5.3 和 lock 组件

<?php

namespace App\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Routing\Annotation\Route;

class LockTest extends AbstractController
{
    /**
     * @Route("/test")
     * @Template("lock/test.html.twig")
     */
    public function test(LockFactory $factory): array
    {
        $lock = $factory->createLock("test");

        $acquired = $lock->acquire();

        dump($lock, $acquired);

        sleep(2);

        dump($lock->isAcquired());

        return ["message" => "testing"];
    }
}

【问题讨论】:

  • 不熟悉锁组件,但如果您的实体有一个唯一的数据库约束(除了 id),那么只会创建一个。尝试创建另一个只会导致抛出异常。因此,如果您只需要防止重复实体,那么您需要查看如何添加唯一数据库约束。
  • 因此,您在一个浏览器中同时打开同一个页面两次,并看到在这两种情况下都获得了锁定,对吧?我认为不是 symfony 锁组件允许您两次获取一个锁。它可能只是普通的php会话锁定:当两个请求同时运行时,第一个获得锁,而第二个被同一个会话锁定。当第一个请求完成时,第二个请求被解锁并成功获取锁。所以首先尝试确保会话被禁用。
  • 可以尝试在Symfony's Discussion Board 上发布您的问题。这将提高锁定专家看到它的机会。另外,我假设您正在使用 apache 或 nginx 等实际生产服务器进行测试?开发服务器一次严格来说是一个请求,并且肯定会扭曲这些事情。
  • @Cerad symfony 的唯一实体约束不是数据库约束而是验证器约束。这会在验证时而不是在插入时进行检查,因此可能会发生竞争条件
  • @xtx 是的,我认为它已锁定到会话,php 是否将请求锁定到某个会话?因为在我的用例中,用户已登录,但我想防止用户多次单击并创建重复的实体。

标签: php symfony locking symfony-lock


【解决方案1】:

我像这样稍微重写了你的控制器(使用 symfony 5.4 和 php 8.1):

class LockTestController extends AbstractController
{
    #[Route("/test")]
    public function test(LockFactory $factory): JsonResponse
    {
        $lock = $factory->createLock("test");

        $t0 = microtime(true);
        $acquired = $lock->acquire(true);
        $acquireTime = microtime(true) - $t0;

        sleep(2);

        return new JsonResponse(["acquired" => $acquired, "acquireTime" => $acquireTime]);
    }
}

它等待锁被释放并计算控制器等待获得锁的时间。

我用 curl 对球童服务器运行了两个请求。

curl -k 'https://localhost/test' & curl -k 'https://localhost/test'

输出确认一个请求被延迟,而第一个请求与获取的锁一起睡觉。

{"acquired":true,"acquireTime":0.0006971359252929688}
{"acquired":true,"acquireTime":2.087146043777466}

如果锁没有阻塞:

$acquired = $lock->acquire(false);

输出是:

{"acquired":true,"acquireTime":0.0007710456848144531}
{"acquired":false,"acquireTime":0.00048804283142089844}

注意没有获得第二个锁。

尽管取得了这些令人鼓舞的结果,doc 还是提到了以下说明:

与其他实现不同,即使锁定实例是为同一资源创建的,锁定组件也会区分它们。这意味着对于给定的范围和资源,可以多次获取一个锁实例。如果一个锁必须由多个服务使用,它们应该共享 LockFactory::createLock 方法返回的同一个 Lock 实例。

我知道两个不同工厂获得的两个锁不应该互相阻塞。除非注释已过时或措辞错误,否则在某些情况下似乎有可能出现不工作的锁。但不是用上面的测试代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-02
    • 2012-03-30
    • 2017-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-20
    • 1970-01-01
    相关资源
    最近更新 更多