Skip to content

Commit 56f71e2

Browse files
committed
Fixed a bug where there could be multiple open sessions by the same session heartbeat.
1 parent 11b9c15 commit 56f71e2

8 files changed

+100
-25
lines changed

Source/Api/Exceptionless.Api.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@
7474
<HintPath>..\..\packages\Foundatio.Parsers.LuceneQueries.1.0.87\lib\net46\Foundatio.Parsers.LuceneQueries.dll</HintPath>
7575
<Private>True</Private>
7676
</Reference>
77-
<Reference Include="Foundatio.Repositories, Version=2.0.255.0, Culture=neutral, processorArchitecture=MSIL">
78-
<HintPath>..\..\packages\Foundatio.Repositories.2.0.255-pre\lib\net46\Foundatio.Repositories.dll</HintPath>
77+
<Reference Include="Foundatio.Repositories, Version=2.0.257.0, Culture=neutral, processorArchitecture=MSIL">
78+
<HintPath>..\..\packages\Foundatio.Repositories.2.0.257\lib\net46\Foundatio.Repositories.dll</HintPath>
7979
<Private>True</Private>
8080
</Reference>
81-
<Reference Include="Foundatio.Repositories.Elasticsearch, Version=2.0.255.0, Culture=neutral, processorArchitecture=MSIL">
82-
<HintPath>..\..\packages\Foundatio.Repositories.Elasticsearch.2.0.255-pre\lib\net46\Foundatio.Repositories.Elasticsearch.dll</HintPath>
81+
<Reference Include="Foundatio.Repositories.Elasticsearch, Version=2.0.257.0, Culture=neutral, processorArchitecture=MSIL">
82+
<HintPath>..\..\packages\Foundatio.Repositories.Elasticsearch.2.0.257\lib\net46\Foundatio.Repositories.Elasticsearch.dll</HintPath>
8383
<Private>True</Private>
8484
</Reference>
8585
<Reference Include="McSherry.SemanticVersioning, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">

Source/Api/packages.config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<package id="Foundatio" version="4.2.1183" targetFramework="net461" />
88
<package id="Foundatio.Parsers.ElasticQueries" version="1.0.87" targetFramework="net461" />
99
<package id="Foundatio.Parsers.LuceneQueries" version="1.0.87" targetFramework="net461" />
10-
<package id="Foundatio.Repositories" version="2.0.255-pre" targetFramework="net461" />
11-
<package id="Foundatio.Repositories.Elasticsearch" version="2.0.255-pre" targetFramework="net461" />
10+
<package id="Foundatio.Repositories" version="2.0.257" targetFramework="net461" />
11+
<package id="Foundatio.Repositories.Elasticsearch" version="2.0.257" targetFramework="net461" />
1212
<package id="McSherry.SemanticVersioning" version="1.2.0" targetFramework="net461" />
1313
<package id="Microsoft.AspNet.Cors" version="5.2.3" targetFramework="net461" />
1414
<package id="Microsoft.AspNet.SignalR.Core" version="2.2.1" targetFramework="net461" />

Source/Core/Exceptionless.Core.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,12 @@
6969
<HintPath>..\..\packages\Foundatio.Parsers.LuceneQueries.1.0.87\lib\net46\Foundatio.Parsers.LuceneQueries.dll</HintPath>
7070
<Private>True</Private>
7171
</Reference>
72-
<Reference Include="Foundatio.Repositories, Version=2.0.255.0, Culture=neutral, processorArchitecture=MSIL">
73-
<HintPath>..\..\packages\Foundatio.Repositories.2.0.255-pre\lib\net46\Foundatio.Repositories.dll</HintPath>
72+
<Reference Include="Foundatio.Repositories, Version=2.0.257.0, Culture=neutral, processorArchitecture=MSIL">
73+
<HintPath>..\..\packages\Foundatio.Repositories.2.0.257\lib\net46\Foundatio.Repositories.dll</HintPath>
7474
<Private>True</Private>
7575
</Reference>
76-
<Reference Include="Foundatio.Repositories.Elasticsearch, Version=2.0.255.0, Culture=neutral, processorArchitecture=MSIL">
77-
<HintPath>..\..\packages\Foundatio.Repositories.Elasticsearch.2.0.255-pre\lib\net46\Foundatio.Repositories.Elasticsearch.dll</HintPath>
76+
<Reference Include="Foundatio.Repositories.Elasticsearch, Version=2.0.257.0, Culture=neutral, processorArchitecture=MSIL">
77+
<HintPath>..\..\packages\Foundatio.Repositories.Elasticsearch.2.0.257\lib\net46\Foundatio.Repositories.Elasticsearch.dll</HintPath>
7878
<Private>True</Private>
7979
</Reference>
8080
<Reference Include="MaxMind.Db, Version=2.0.0.0, Culture=neutral, PublicKeyToken=66afa4cc5ae853ac, processorArchitecture=MSIL">

Source/Core/Jobs/CloseInactiveSessionsJob.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,18 @@ protected override async Task<JobResult> RunInternalAsync(JobContext context) {
3636
var inactivePeriodUtc = SystemClock.UtcNow.Subtract(DefaultInactivePeriod);
3737
var sessionsToUpdate = new List<PersistentEvent>(results.Documents.Count);
3838
var cacheKeysToRemove = new List<string>(results.Documents.Count * 2);
39+
var existingSessionHeartbeatIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
3940

4041
foreach (var sessionStart in results.Documents) {
4142
var lastActivityUtc = sessionStart.Date.UtcDateTime.AddSeconds((double)sessionStart.Value.GetValueOrDefault());
4243
var heartbeatResult = await GetHeartbeatAsync(sessionStart).AnyContext();
4344

44-
if (heartbeatResult != null && (heartbeatResult.Close || heartbeatResult.ActivityUtc > lastActivityUtc))
45-
sessionStart.UpdateSessionStart(heartbeatResult.ActivityUtc, isSessionEnd: heartbeatResult.Close || heartbeatResult.ActivityUtc <= inactivePeriodUtc);
45+
bool closeDuplicate = heartbeatResult?.CacheKey != null && existingSessionHeartbeatIds.Contains(heartbeatResult.CacheKey);
46+
if (heartbeatResult?.CacheKey != null && !closeDuplicate)
47+
existingSessionHeartbeatIds.Add(heartbeatResult.CacheKey);
48+
49+
if (heartbeatResult != null && (closeDuplicate || heartbeatResult.Close || heartbeatResult.ActivityUtc > lastActivityUtc))
50+
sessionStart.UpdateSessionStart(heartbeatResult.ActivityUtc, isSessionEnd: closeDuplicate || heartbeatResult.Close || heartbeatResult.ActivityUtc <= inactivePeriodUtc);
4651
else if (lastActivityUtc <= inactivePeriodUtc)
4752
sessionStart.UpdateSessionStart(lastActivityUtc, isSessionEnd: true);
4853
else

Source/Core/packages.config

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<package id="Foundatio" version="4.2.1183" targetFramework="net461" />
88
<package id="Foundatio.Parsers.ElasticQueries" version="1.0.87" targetFramework="net461" />
99
<package id="Foundatio.Parsers.LuceneQueries" version="1.0.87" targetFramework="net461" />
10-
<package id="Foundatio.Repositories" version="2.0.255-pre" targetFramework="net461" />
11-
<package id="Foundatio.Repositories.Elasticsearch" version="2.0.255-pre" targetFramework="net461" />
10+
<package id="Foundatio.Repositories" version="2.0.257" targetFramework="net461" />
11+
<package id="Foundatio.Repositories.Elasticsearch" version="2.0.257" targetFramework="net461" />
1212
<package id="MaxMind.Db" version="2.1.2" targetFramework="net461" />
1313
<package id="MaxMind.GeoIP2" version="2.7.1" targetFramework="net461" />
1414
<package id="McSherry.SemanticVersioning" version="1.2.0" targetFramework="net461" />

Source/Tests/Exceptionless.Api.Tests.csproj

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@
3535
</PropertyGroup>
3636
<ItemGroup>
3737
<Reference Include="ApprovalTests, Version=3.0.0.0, Culture=neutral, PublicKeyToken=11bd7d124fc62e0f, processorArchitecture=MSIL">
38-
<HintPath>..\..\packages\ApprovalTests.3.0.11\lib\net40\ApprovalTests.dll</HintPath>
38+
<HintPath>..\..\packages\ApprovalTests.3.0.12\lib\net40\ApprovalTests.dll</HintPath>
3939
<Private>True</Private>
4040
</Reference>
4141
<Reference Include="ApprovalUtilities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=11bd7d124fc62e0f, processorArchitecture=MSIL">
42-
<HintPath>..\..\packages\ApprovalUtilities.3.0.11\lib\net45\ApprovalUtilities.dll</HintPath>
42+
<HintPath>..\..\packages\ApprovalUtilities.3.0.12\lib\net45\ApprovalUtilities.dll</HintPath>
4343
<Private>True</Private>
4444
</Reference>
4545
<Reference Include="ApprovalUtilities.Net45, Version=3.0.0.0, Culture=neutral, processorArchitecture=MSIL">
46-
<HintPath>..\..\packages\ApprovalUtilities.3.0.11\lib\net45\ApprovalUtilities.Net45.dll</HintPath>
46+
<HintPath>..\..\packages\ApprovalUtilities.3.0.12\lib\net45\ApprovalUtilities.Net45.dll</HintPath>
4747
<Private>True</Private>
4848
</Reference>
4949
<Reference Include="Elasticsearch.Net, Version=1.0.0.0, Culture=neutral, PublicKeyToken=96c599bbe3e70f5d, processorArchitecture=MSIL">
@@ -78,12 +78,12 @@
7878
<HintPath>..\..\packages\Foundatio.Parsers.LuceneQueries.1.0.87\lib\net46\Foundatio.Parsers.LuceneQueries.dll</HintPath>
7979
<Private>True</Private>
8080
</Reference>
81-
<Reference Include="Foundatio.Repositories, Version=2.0.255.0, Culture=neutral, processorArchitecture=MSIL">
82-
<HintPath>..\..\packages\Foundatio.Repositories.2.0.255-pre\lib\net46\Foundatio.Repositories.dll</HintPath>
81+
<Reference Include="Foundatio.Repositories, Version=2.0.257.0, Culture=neutral, processorArchitecture=MSIL">
82+
<HintPath>..\..\packages\Foundatio.Repositories.2.0.257\lib\net46\Foundatio.Repositories.dll</HintPath>
8383
<Private>True</Private>
8484
</Reference>
85-
<Reference Include="Foundatio.Repositories.Elasticsearch, Version=2.0.255.0, Culture=neutral, processorArchitecture=MSIL">
86-
<HintPath>..\..\packages\Foundatio.Repositories.Elasticsearch.2.0.255-pre\lib\net46\Foundatio.Repositories.Elasticsearch.dll</HintPath>
85+
<Reference Include="Foundatio.Repositories.Elasticsearch, Version=2.0.257.0, Culture=neutral, processorArchitecture=MSIL">
86+
<HintPath>..\..\packages\Foundatio.Repositories.Elasticsearch.2.0.257\lib\net46\Foundatio.Repositories.Elasticsearch.dll</HintPath>
8787
<Private>True</Private>
8888
</Reference>
8989
<Reference Include="MaxMind.Db, Version=2.0.0.0, Culture=neutral, PublicKeyToken=66afa4cc5ae853ac, processorArchitecture=MSIL">

Source/Tests/Jobs/CloseInactiveSessionsJobTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,76 @@ public CloseInactiveSessionsJobTests(ITestOutputHelper output) : base(output) {
3737
CreateDataAsync().GetAwaiter().GetResult();
3838
}
3939

40+
[Fact]
41+
public async Task CloseDuplicateIdentitySessions() {
42+
const string userId = "blake@exceptionless.io";
43+
var event1 = GenerateEvent(SystemClock.OffsetNow.SubtractMinutes(5), userId);
44+
var event2 = GenerateEvent(SystemClock.OffsetNow.SubtractMinutes(5), userId, sessionId: "123456789");
45+
46+
var contexts = await _pipeline.RunAsync(new []{ event1, event2 });
47+
Assert.True(contexts.All(c => !c.HasError));
48+
Assert.True(contexts.All(c => !c.IsCancelled));
49+
Assert.True(contexts.All(c => c.IsProcessed));
50+
51+
await _configuration.Client.RefreshAsync();
52+
var events = await _eventRepository.GetAllAsync();
53+
Assert.Equal(4, events.Total);
54+
Assert.Equal(2, events.Documents.Where(e => !String.IsNullOrEmpty(e.GetSessionId())).Select(e => e.GetSessionId()).Distinct().Count());
55+
var sessionStarts = events.Documents.Where(e => e.IsSessionStart()).ToList();
56+
Assert.Equal(0, sessionStarts.Sum(e => e.Value));
57+
Assert.False(sessionStarts.Any(e => e.HasSessionEndTime()));
58+
59+
var utcNow = SystemClock.UtcNow;
60+
await _cache.SetAsync($"Project:{sessionStarts.First().ProjectId}:heartbeat:{userId.ToSHA1()}", utcNow.SubtractMinutes(1));
61+
62+
_job.DefaultInactivePeriod = TimeSpan.FromMinutes(3);
63+
Assert.Equal(JobResult.Success, await _job.RunAsync());
64+
await _configuration.Client.RefreshAsync();
65+
events = await _eventRepository.GetAllAsync();
66+
Assert.Equal(4, events.Total);
67+
68+
sessionStarts = events.Documents.Where(e => e.IsSessionStart()).ToList();
69+
Assert.Equal(2, sessionStarts.Count);
70+
Assert.Equal(1, sessionStarts.Count(e => !e.HasSessionEndTime()));
71+
Assert.Equal(1, sessionStarts.Count(e => e.HasSessionEndTime()));
72+
}
73+
74+
[Fact]
75+
public async Task WillNotCloseDuplicateIdentitySessionsWithSessionIdHeartbeat() {
76+
const string userId = "blake@exceptionless.io";
77+
const string sessionId = "123456789";
78+
var event1 = GenerateEvent(SystemClock.OffsetNow.SubtractMinutes(5), userId);
79+
var event2 = GenerateEvent(SystemClock.OffsetNow.SubtractMinutes(5), userId, sessionId: sessionId);
80+
81+
var contexts = await _pipeline.RunAsync(new[] { event1, event2 });
82+
Assert.True(contexts.All(c => !c.HasError));
83+
Assert.True(contexts.All(c => !c.IsCancelled));
84+
Assert.True(contexts.All(c => c.IsProcessed));
85+
86+
await _configuration.Client.RefreshAsync();
87+
var events = await _eventRepository.GetAllAsync();
88+
Assert.Equal(4, events.Total);
89+
Assert.Equal(2, events.Documents.Where(e => !String.IsNullOrEmpty(e.GetSessionId())).Select(e => e.GetSessionId()).Distinct().Count());
90+
var sessionStarts = events.Documents.Where(e => e.IsSessionStart()).ToList();
91+
Assert.Equal(0, sessionStarts.Sum(e => e.Value));
92+
Assert.False(sessionStarts.Any(e => e.HasSessionEndTime()));
93+
94+
var utcNow = SystemClock.UtcNow;
95+
await _cache.SetAsync($"Project:{sessionStarts.First().ProjectId}:heartbeat:{userId.ToSHA1()}", utcNow.SubtractMinutes(1));
96+
await _cache.SetAsync($"Project:{sessionStarts.First().ProjectId}:heartbeat:{sessionId.ToSHA1()}", utcNow.SubtractMinutes(1));
97+
98+
_job.DefaultInactivePeriod = TimeSpan.FromMinutes(3);
99+
Assert.Equal(JobResult.Success, await _job.RunAsync());
100+
await _configuration.Client.RefreshAsync();
101+
events = await _eventRepository.GetAllAsync();
102+
Assert.Equal(4, events.Total);
103+
104+
sessionStarts = events.Documents.Where(e => e.IsSessionStart()).ToList();
105+
Assert.Equal(2, sessionStarts.Count);
106+
Assert.Equal(2, sessionStarts.Count(e => !e.HasSessionEndTime()));
107+
Assert.Equal(0, sessionStarts.Count(e => e.HasSessionEndTime()));
108+
}
109+
40110
[Theory]
41111
[InlineData(1, true, null, false)]
42112
[InlineData(1, true, 70, false)]

Source/Tests/packages.config

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3-
<package id="ApprovalTests" version="3.0.11" targetFramework="net461" />
4-
<package id="ApprovalUtilities" version="3.0.11" targetFramework="net461" />
3+
<package id="ApprovalTests" version="3.0.12" targetFramework="net461" />
4+
<package id="ApprovalUtilities" version="3.0.12" targetFramework="net461" />
55
<package id="Elasticsearch.Net" version="1.9.1" targetFramework="net461" />
66
<package id="Exceptionless.DateTimeExtensions" version="3.2.53" targetFramework="net461" />
77
<package id="Exceptionless.RandomData" version="1.1.24" targetFramework="net461" />
@@ -10,8 +10,8 @@
1010
<package id="Foundatio.Logging.Xunit" version="4.2.1183" targetFramework="net461" />
1111
<package id="Foundatio.Parsers.ElasticQueries" version="1.0.87" targetFramework="net461" />
1212
<package id="Foundatio.Parsers.LuceneQueries" version="1.0.87" targetFramework="net461" />
13-
<package id="Foundatio.Repositories" version="2.0.255-pre" targetFramework="net461" />
14-
<package id="Foundatio.Repositories.Elasticsearch" version="2.0.255-pre" targetFramework="net461" />
13+
<package id="Foundatio.Repositories" version="2.0.257" targetFramework="net461" />
14+
<package id="Foundatio.Repositories.Elasticsearch" version="2.0.257" targetFramework="net461" />
1515
<package id="MaxMind.Db" version="2.1.2" targetFramework="net461" />
1616
<package id="MaxMind.GeoIP2" version="2.7.1" targetFramework="net461" />
1717
<package id="McSherry.SemanticVersioning" version="1.2.0" targetFramework="net461" />

0 commit comments

Comments
 (0)