Azure / azure-signalr

Azure SignalR Service SDK for .NET

Home Page:https://aka.ms/signalr-service

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is IUserIdProvider.GetUserId(HubConnectionContext connection) thread safe?

co-dax opened this issue · comments

commented

IUserIdProvider has to be registered as a signleton but I couldn't find anywhere whether the method IUserIdProvider.GetUserId(HubConnectionContext connection) is thread safe. Is it thread safe or should I serialize access to it from different requests/threads?

Thanks.

commented

It depends on the implementation. The default implementation https://github.com/dotnet/aspnetcore/blob/3265dc6a9b05c74b199aa39351e5413317df9ad5/src/SignalR/server/Core/src/DefaultUserIdProvider.cs only accesses the connection, so it can be viewed as thread safe.

commented

Thanks @Y-Sindo.

The specific question is whether I need to use lock (lockObj) in the following piece of code which is the implementation for IUserIdProvider.GetUserId(HubConnectionContext connection):

public string? GetUserId(HubConnectionContext connection)
        {
            lock (lockObj)
            {                
                var userId = connection.GetHttpContext().User?.FindFirstValue(ClaimTypes.NameIdentifier);
                using (var scope = _scopeFactory.CreateScope())
                {
                    var signalRUserIdGenerator = scope.ServiceProvider.GetRequiredService<ISignalRUserIdGenerator>();
                    var tenantId = connection.GetHttpContext().Items["TenantId"]?.ToString();

                    return signalRUserIdGenerator.GetUserConnectionId(userId, tenantId);

                }
            }
        }

I wouldn't say locking is required in this case as the method is neither using class fields nor is there any lambda expression or callback that could access GetUserId method variables from another thread. It would say it shouldn't be required even if the calls by the framework to IUserIdProvider.GetUserId(HubConnectionContext connection) are not serialized.

What are your thoughts on this?

Thanks!

commented

First of all, the calls to IUserIdProvider.GetUserId(HubConnectionContext connection) by the framework are not serialized. You should only use lock when all of your ISignalRUserIdGenerator instances need to modify a piece of shared memory. However, I don't think it's very likely to be the truth.

commented

The problem is that there can only be one instance of IUserIdProvider implementation as framework requires it to be registered as a signleton so that's why I am actually asking but I would say you have provided the crucial informaiton saying that the framework is not serializing the calls to IUserIdProvider.GetUserId(HubConnectionContext connection) but that would imply that the method from my previous comment would need locking as, since IUserIdProvider must be a singleton and calls to IUserIdProvider.GetUserId(HubConnectionContext connection) are not serialized by the framework, in the code from below it may happen the following:

  1. At line with comment "1" thread A stores value "x" in variable userId
  2. Thread B steps in at the same line with comment "1" and sets userId to value "y"
  3. Thread A steps in again (resumes) by line with comment 2
  4. But now thread A will read value from variable userId that has been set by thread B, that is, value "y"
public string? GetUserId(HubConnectionContext connection)
        {
            lock (lockObj)
            {                
                var userId = connection.GetHttpContext().User?.FindFirstValue(ClaimTypes.NameIdentifier); //  1

                using (var scope = _scopeFactory.CreateScope()) // 2
                {
                    var signalRUserIdGenerator = scope.ServiceProvider.GetRequiredService<ISignalRUserIdGenerator>();
                    var tenantId = connection.GetHttpContext().Items["TenantId"]?.ToString();

                    return signalRUserIdGenerator.GetUserConnectionId(userId, tenantId);

                }
            }
        }

Am I missing something?

Let me know if I was not clear.

commented

userId , tenantId and signalRUserIdGenerator such variables are local variables that are only accessed by one thread. Each thread would have its own copy of the variables. Therefore, you don't need to consider thread-safety problem regarding to those variables.

commented

Thanks @Y-Sindo. As for _scopeFactory, it is a class level field coming from a service injected through the constructor but since it is nowhere else assinged then it is safe for it to be used by multiple threads. Right?

commented

I assume the _scopeFactory is IServiceLevelScopeFactory or IServiceProvider. _scopeFactory.CreateScope() is thread-safe but isn't due to the reason you say. Both are singleton and you can get the same instances anywhere else by ServiceProvider.GetRequiredService<> or requesting them in the constructors of a service created by the service container. Therefore, they can be assigned anywhere else. We can see in the ASP.NET Core documents that this method is never used inside a lock, so we know it's thread safe. Also, we can such a hot path operation should be thread safe.

commented

You assumptions are correct.
I get what you mean, I didn't take into consideration that IServiceScopeFactory is singleton as well.

I am closing this ticket.

Thanks for your expertise and time. All the best!