Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Dispose Hubs from ActivatorUtilities #44

Merged
merged 1 commit into from
Dec 6, 2016
Merged

Dispose Hubs from ActivatorUtilities #44

merged 1 commit into from
Dec 6, 2016

Conversation

BrennanConroy
Copy link
Member

We weren't disposing the hubs from Activator.CreateInstance<THub>() in a couple places

@moozzyk @anurse @davidfowl @mikaelm12 @muratg

@dnfclas
Copy link

dnfclas commented Nov 21, 2016

Hi @BrennanConroy, I'm your friendly neighborhood .NET Foundation Pull Request Bot (You can call me DNFBOT). Thanks for your contribution!
You've already signed the contribution license agreement. Thanks!

The agreement was validated by .NET Foundation and real humans are currently evaluating your PR.

TTYL, DNFBOT;


return hub;
}

private void InitializeHub(Connection connection, THub hub)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still need this method? Why not inline it in CreateHub?

@@ -63,9 +63,16 @@ public class HubEndPoint<THub, TClient> : EndPoint where THub : Hub<TClient>

using (var scope = _serviceScopeFactory.CreateScope())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remind me why we have separate scopes for Connected/Disconnected?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's because we are simulating scoped services per "request" so connected is the first request and disconnected is from a different request.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hubs are transient and a scope needs to be created whenever we make a hub. We'll need to see how bad the performance is for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that for invocations hubs are transient. I was wondering if it has to be the case for onconnected/disconnected but I think you are right - making them non-transient here would be really weird.

@@ -63,9 +63,16 @@ public class HubEndPoint<THub, TClient> : EndPoint where THub : Hub<TClient>

using (var scope = _serviceScopeFactory.CreateScope())
{
var hub = scope.ServiceProvider.GetService<THub>() ?? Activator.CreateInstance<THub>();
InitializeHub(connection, hub);
bool created;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var created = false?

Also, I'd like someone to take a look at the name created in this context (and in CreateHub method below). It's set to true only if it's created by Activator, though even if it's coming through a GetService call, someone have "created" it before :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool created is fine. In general I find this pattern ugly and it is now across the entire file but honestly, it works.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's really a design flaw with the pattern. We need a HubActivator with a Create and Dispose like MVC's ControllerActivator (or is it the factory that disposes..)

Copy link
Member

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test

@BrennanConroy
Copy link
Member Author

Added a test

if (created)
{
// Dispose the object if it's disposable and we created it
(hub as IDisposable)?.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the cast?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Odd, I thought I copied it from somewhere...

{
var endPointTask = Task.Run(() => endPoint.OnConnectedAsync(connectionWrapper.Connection));

Thread.Sleep(100);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure what to do here, don't want to await endPoint.OnConnectedAsync(...) because it wont complete, and need to wait a small amount of time for endPoint.OnConnectedAsync(...) to actually run and get to DispatchMessagesAsync

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you have a handle on the Pipeline that was created and passed into OnConnectedAsync, you can get access to the PipelineReaderWriter and await ReadingStarted.


private class TestHub : Hub
{
static public int DisposedCount = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this static?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can Assert that dispose was called on connect and on disconnect.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should pass in an object that has the state and not use a static for this.


if (hub == null)
{
hub = ActivatorUtilities.CreateInstance<THub>(provider);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: Changed this so that hubs with non-default constructors work without adding the hub to DI

{
if (dispose)
{
Interlocked.Increment(ref _trackDispose.DisposeCount);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this interlocked?

{
var endPointTask = Task.Run(() => endPoint.OnConnectedAsync(connectionWrapper.Connection));

await ((HttpConnection)connectionWrapper.Connection.Channel).Input.ReadingStarted;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expose the HttpConnection directly on connectionWrapper so you can avoid the cast


await endPointTask;

Assert.Equal(2, trackDispose.DisposeCount);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it 2?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnConnectedAsync + OnDisconnectedAsync

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok.

@@ -0,0 +1,105 @@
using System;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

License and copyright header

public async Task HubsAreDisposed()
{
var trackDispose = new TrackDispose();
var serviceProvider = CreateServiceProvider((s) => s.AddSingleton(trackDispose));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: parens not needed in (s)

"dotnet-test-xunit": "2.2.0-*",
"Microsoft.AspNetCore.Hosting": "1.2.0-*",
"Microsoft.AspNetCore.Sockets": "0.1.0-*",
"Microsoft.AspNetCore.SignalR": "1.0.0-*",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be project reference

var user = new ClaimsPrincipal(new ClaimsIdentity());
Connection = connectionManager.AddNewConnection(_httpConnection).Connection;
Connection.Metadata["formatType"] = format;
Connection.User = user;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

golfing: initialize directly (i.e. without the user variable)


using (var connectionWrapper = new ConnectionWrapper())
{
var endPointTask = Task.Run(() => endPoint.OnConnectedAsync(connectionWrapper.Connection));
Copy link
Contributor

@moozzyk moozzyk Nov 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't it be just var endPointTask = endPoint.OnConnectedAsync(connectionWrapper.Connection); ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... good catch

@davidfowl
Copy link
Member

Next, this logic needs to move into a hub activator like component.

<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DNX? What version of VS do you have??

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants