Skip to content

Commit

Permalink
Deterministic session management. (#1268)
Browse files Browse the repository at this point in the history
* Deterministic session management.

* Create session collection.

* Fix test.

* Fix theme test.

* Remove wait dbugger.
  • Loading branch information
adamdriscoll authored Oct 17, 2019
1 parent e70a84e commit 5c12794
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Describe "Element" {
}

New-UDElement -Tag "div" -Id "sessionInfo" -Endpoint {
$DashboardService.EndpointService.Sessions[$SessionId].Endpoints.Count
$DashboardService.EndpointService.SessionManager.GetSession($SessionId).Endpoints.Count
} -AutoRefresh -RefreshInterval 1
}

Expand Down
3 changes: 1 addition & 2 deletions src/UniversalDashboard.UITest/Integration/Theme.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,7 @@ Describe "Theme" {
$Server.DashboardService.SetDashboard($Dashboard)
It "should generate the correct theme" {
$Theme = Invoke-WebRequest http://localhost:10001/api/internal/dashboard/theme -WebSession $ud

$Theme.Content.Contains(".ud-dashboard {`r`n`tbackground-color : #EEEEEE;`r`n`tcolor : #111111;`r`n}`r`n.ud-table {`r`n`tbackground-color : #123123;`r`n}`r`n") | should be $true
$Theme.Content.Contains(".ud-dashboard {`r`n`tbackground-color : #234234;`r`n`tcolor : #959595;`r`n}`r`n.ud-table {`r`n`tbackground-color : #123123;`r`n}`r`n") | should be $true
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using UniversalDashboard.Interfaces;
using System.Text.RegularExpressions;
using System.Collections.Concurrent;
using UniversalDashboard.Services;

namespace UniversalDashboard.Execution
{
Expand All @@ -19,7 +20,7 @@ public class EndpointService : IEndpointService
private static IEndpointService _instance;

public ConcurrentDictionary<string, Endpoint> Endpoints { get; private set; }
public ConcurrentDictionary<string, SessionState> Sessions { get; private set; }
public ISessionManager SessionManager { get; private set; }

public static IEndpointService Instance
{
Expand All @@ -37,38 +38,12 @@ public static IEndpointService Instance
private EndpointService()
{
Endpoints = new ConcurrentDictionary<string, Endpoint>();
Sessions = new ConcurrentDictionary<string, SessionState>();
SessionManager = new SessionManager();

_restEndpoints = new List<Endpoint>();
_scheduledEndpoints = new List<Endpoint>();
}

public void StartSession(string sessionId)
{
if (Sessions.ContainsKey(sessionId))
{
var session = Sessions[sessionId];
session.Connections++;
}
else
{
Sessions.TryAdd(sessionId, new SessionState(sessionId));
}
}

public void EndSession(string sessionId)
{
var session = Sessions[sessionId];
if (session.Connections <= 1)
{
Sessions.TryRemove(sessionId, out SessionState value);
}
else
{
session.Connections--;
}
}

public void Register(Endpoint callback)
{
if (callback.ScriptBlock == null)
Expand All @@ -92,14 +67,14 @@ public void Register(Endpoint callback)
}
else
{
if (!Sessions.ContainsKey(callback.SessionId))
if (!SessionManager.SessionExists(callback.SessionId))
{
StartSession(callback.SessionId);
SessionManager.StartSession(callback.SessionId);
}

if (Sessions.ContainsKey(callback.SessionId))
if (SessionManager.SessionExists(callback.SessionId))
{
var session = Sessions[callback.SessionId];
var session = SessionManager.GetSession(callback.SessionId);
session.Endpoints.TryAdd(callback.Name, callback);
}
}
Expand Down Expand Up @@ -138,9 +113,9 @@ public void Unregister(string name, string sessionId)
}
else
{
if (Sessions.ContainsKey(sessionId))
if (SessionManager.SessionExists(sessionId))
{
var session = Sessions[sessionId];
var session = SessionManager.GetSession(sessionId);
if (session.Endpoints.ContainsKey(name))
{
logger.Debug("Session endpoint found. Removing endpoint.");
Expand All @@ -155,9 +130,9 @@ public Endpoint Get(string name, string sessionId)
logger.Debug($"Get() {name} {sessionId}");
if (sessionId != null)
{
if (Sessions.ContainsKey(sessionId))
if (SessionManager.SessionExists(sessionId))
{
var session = Sessions[sessionId];
var session = SessionManager.GetSession(sessionId);
if (session.Endpoints.ContainsKey(name))
{
logger.Debug("Found session endpoint.");
Expand Down
21 changes: 21 additions & 0 deletions src/UniversalDashboard/Execution/ScheduledEndpointManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading;
using System.Threading.Tasks;
using UniversalDashboard.Interfaces;
using UniversalDashboard.Services;

namespace UniversalDashboard.Execution
{
Expand Down Expand Up @@ -138,6 +139,26 @@ public async Task StartAsync(CancellationToken cancellationToken)
}
await _scheduler.ScheduleJob(job, trigger);
}

if (_dashboardService.Dashboard != null)
{
var dataMap = new JobDataMap();
dataMap.Add(nameof(SessionTimeoutJob.IdleTimeout), _dashboardService.Dashboard.IdleTimeout);

var job = JobBuilder.Create<SessionTimeoutJob>()
.UsingJobData(dataMap)
.Build();

var trigger = TriggerBuilder.Create()
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(60)
.RepeatForever())
.Build();

await _scheduler.ScheduleJob(job, trigger);
}

}

public async Task StopAsync(CancellationToken cancellationToken)
Expand Down
28 changes: 14 additions & 14 deletions src/UniversalDashboard/Execution/SessionDriveProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ private string SessionId

protected override void GetItem(string name)
{
if (EndpointService.Instance.Sessions.ContainsKey(SessionId))
if (EndpointService.Instance.SessionManager.SessionExists(SessionId))
{
var value = EndpointService.Instance.Sessions[SessionId].GetVariableValue(name);
var value = EndpointService.Instance.SessionManager.GetSession(SessionId).GetVariableValue(name);
if (value != null)
{
base.WriteItemObject(value, name.ToLower(), false);
Expand All @@ -68,25 +68,25 @@ protected override void GetItem(string name)

protected override void NewItem(string path, string itemTypeName, object newItemValue)
{
if (EndpointService.Instance.Sessions.ContainsKey(SessionId))
if (EndpointService.Instance.SessionManager.SessionExists(SessionId))
{
EndpointService.Instance.Sessions[SessionId].SetVariable(path, newItemValue);
EndpointService.Instance.SessionManager.GetSession(SessionId).SetVariable(path, newItemValue);
}
}

protected override void SetItem(string name, object value)
{
if (EndpointService.Instance.Sessions.ContainsKey(SessionId))
if (EndpointService.Instance.SessionManager.SessionExists(SessionId))
{
EndpointService.Instance.Sessions[SessionId].SetVariable(name, value);
EndpointService.Instance.SessionManager.GetSession(SessionId).SetVariable(name, value);
}
}

protected override bool ItemExists(string path)
{
if (EndpointService.Instance.Sessions.ContainsKey(SessionId))
if (EndpointService.Instance.SessionManager.SessionExists(SessionId))
{
return EndpointService.Instance.Sessions[SessionId].SessionVariables.ContainsKey(path.ToLower());
return EndpointService.Instance.SessionManager.GetSession(SessionId).SessionVariables.ContainsKey(path.ToLower());
}
return false;
}
Expand All @@ -98,9 +98,9 @@ protected override bool IsValidPath(string path)

public void ClearContent(string path)
{
if (EndpointService.Instance.Sessions.ContainsKey(SessionId))
if (EndpointService.Instance.SessionManager.SessionExists(SessionId))
{
EndpointService.Instance.Sessions[SessionId].RemoveVariable(path);
EndpointService.Instance.SessionManager.GetSession(SessionId).RemoveVariable(path);
}
}

Expand All @@ -113,9 +113,9 @@ public IContentReader GetContentReader(string path)
{
Logger.Debug($"GetContentReader - {path} ");

if (EndpointService.Instance.Sessions.ContainsKey(SessionId))
if (EndpointService.Instance.SessionManager.SessionExists(SessionId))
{
return new SessionStateReaderWriter(path.ToLower(), EndpointService.Instance.Sessions[SessionId]);
return new SessionStateReaderWriter(path.ToLower(), EndpointService.Instance.SessionManager.GetSession(SessionId));
}

return null;
Expand All @@ -130,9 +130,9 @@ public IContentWriter GetContentWriter(string path)
{
Logger.Debug($"GetContentWriter - {path} ");

if (EndpointService.Instance.Sessions.ContainsKey(SessionId))
if (EndpointService.Instance.SessionManager.SessionExists(SessionId))
{
return new SessionStateReaderWriter(path.ToLower(), EndpointService.Instance.Sessions[SessionId]);
return new SessionStateReaderWriter(path.ToLower(), EndpointService.Instance.SessionManager.GetSession(SessionId));
}

return null;
Expand Down
5 changes: 2 additions & 3 deletions src/UniversalDashboard/Interfaces/IEndpointService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using UniversalDashboard.Models;
using UniversalDashboard.Services;

namespace UniversalDashboard.Interfaces
{
Expand All @@ -11,9 +12,7 @@ public interface IEndpointService
Endpoint GetByUrl(string url, string method, Dictionary<string, object> matchedVariables);
IEnumerable<Endpoint> GetScheduledEndpoints();
void Register(Endpoint callback);
void StartSession(string sessionId);
void EndSession(string sessionId);
ConcurrentDictionary<string, Endpoint> Endpoints { get; }
ConcurrentDictionary<string, SessionState> Sessions { get; }
ISessionManager SessionManager { get; }
}
}
5 changes: 4 additions & 1 deletion src/UniversalDashboard/Models/SessionState.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace UniversalDashboard.Models
Expand All @@ -11,12 +12,14 @@ public SessionState(string id)
Endpoints = new ConcurrentDictionary<string, Endpoint>();
SessionVariables = new ConcurrentDictionary<string, object>();
Connections = 1;
LastTouched = DateTime.UtcNow;
}

public string Id { get; set; }
public int Connections { get; set; }
public ConcurrentDictionary<string, Endpoint> Endpoints { get; set; }
public ConcurrentDictionary<string, object> SessionVariables { get; set; }
public DateTime LastTouched { get; set; }

public object GetVariableValue(string name)
{
Expand Down
5 changes: 2 additions & 3 deletions src/UniversalDashboard/Server/DashboardHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using NLog.Fluent;
using UniversalDashboard.Execution;
Expand Down Expand Up @@ -155,7 +154,7 @@ public override async Task OnDisconnectedAsync(Exception exception)
var sessionId = _connectionManager.GetSessionId(Context.ConnectionId);
if (sessionId != null)
{
_dashboardService.EndpointService.EndSession(sessionId as string);
_dashboardService.EndpointService.SessionManager.EndSession(sessionId as string);
}

_connectionManager.RemoveConnection(Context.ConnectionId);
Expand All @@ -166,7 +165,7 @@ public async Task SetSessionId(string sessionId)
Log.Debug($"SetSessionId({sessionId})");

_connectionManager.AddConnection(new Connection { Id = Context.ConnectionId, SessionId = sessionId });
_dashboardService.EndpointService.StartSession(sessionId);
_dashboardService.EndpointService.SessionManager.StartSession(sessionId);

await Clients.All.SendAsync("setConnectionId", Context.ConnectionId);
}
Expand Down
77 changes: 77 additions & 0 deletions src/UniversalDashboard/Services/SessionManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using UniversalDashboard.Models;

namespace UniversalDashboard.Services
{
public interface ISessionManager
{
void StartSession(string id);
void EndSession(string id);
bool SessionExists(string id);
SessionState GetSession(string id);
ConcurrentDictionary<string, SessionState> Sessions { get; }
void ClearTimedOutSessions(TimeSpan timespan);
}

public class SessionManager : ISessionManager
{
public ConcurrentDictionary<string, SessionState> Sessions { get; private set; }

public SessionManager()
{
Sessions = new ConcurrentDictionary<string, SessionState>();
}

public void StartSession(string id)
{
if (Sessions.ContainsKey(id))
{
var session = Sessions[id];
session.LastTouched = DateTime.UtcNow;
session.Connections++;
}
else
{
Sessions.TryAdd(id, new SessionState(id));
}
}

public void EndSession(string id)
{
var session = Sessions[id];
session.LastTouched = DateTime.UtcNow;
session.Connections--;
}

public bool SessionExists(string id)
{
return Sessions.ContainsKey(id);
}

public SessionState GetSession(string id)
{
var session = Sessions[id];
session.LastTouched = DateTime.UtcNow;
return session;
}

public void ClearTimedOutSessions(TimeSpan timespan)
{
var toRemove = new List<string>();
foreach(var key in Sessions.Keys)
{
var session = Sessions[key];
if (session.LastTouched < DateTime.UtcNow - timespan)
{
toRemove.Add(session.Id);
}
}

toRemove.ForEach(x => {
Sessions.TryRemove(x, out SessionState value);
});
}
}
}
17 changes: 17 additions & 0 deletions src/UniversalDashboard/Services/SessionTimeoutJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Threading.Tasks;
using Quartz;
using UniversalDashboard.Execution;

namespace UniversalDashboard.Services
{
public class SessionTimeoutJob : IJob
{
public TimeSpan IdleTimeout { get; set; }
public async Task Execute(IJobExecutionContext context)
{
await Task.CompletedTask;
EndpointService.Instance.SessionManager.ClearTimedOutSessions(IdleTimeout);
}
}
}

0 comments on commit 5c12794

Please sign in to comment.