tgstation-server  4.3.2
The /tg/station 13 server suite
Provider.cs
Go to the documentation of this file.
1 using Microsoft.Extensions.Logging;
2 using System;
3 using System.Collections.Generic;
4 using System.Threading;
5 using System.Threading.Tasks;
7 
8 namespace Tgstation.Server.Host.Components.Chat.Providers
9 {
11  abstract class Provider : IProvider
12  {
16  protected ILogger Logger { get; }
17 
21  readonly Queue<Message> messageQueue;
22 
26  readonly object reconnectTaskLock;
27 
31  TaskCompletionSource<object> nextMessage;
32 
37 
41  CancellationTokenSource reconnectCts;
42 
48  protected Provider(ILogger logger, uint reconnectInterval)
49  {
50  Logger = logger ?? throw new ArgumentNullException(nameof(logger));
51 
52  messageQueue = new Queue<Message>();
53  nextMessage = new TaskCompletionSource<object>();
54 
55  reconnectTaskLock = new object();
56 
57  SetReconnectInterval(reconnectInterval).GetAwaiter().GetResult();
58  logger.LogTrace("Created.");
59  }
60 
62  public abstract bool Connected { get; }
63 
65  public abstract string BotMention { get; }
66 
71  protected void EnqueueMessage(Message message)
72  {
73  lock (messageQueue)
74  {
75  messageQueue.Enqueue(message);
76  nextMessage.TrySetResult(null);
77  }
78  }
79 
81  public virtual void Dispose()
82  {
83  StopReconnectionTimer().GetAwaiter().GetResult();
84  Logger.LogTrace("Disposed");
85  }
86 
88  public abstract Task<bool> Connect(CancellationToken cancellationToken);
89 
91  public abstract Task Disconnect(CancellationToken cancellationToken);
92 
94  public abstract Task<IReadOnlyCollection<ChannelRepresentation>> MapChannels(IEnumerable<Api.Models.ChatChannel> channels, CancellationToken cancellationToken);
95 
97  public async Task<Message> NextMessage(CancellationToken cancellationToken)
98  {
99  var cancelTcs = new TaskCompletionSource<object>();
100  using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
101  await Task.WhenAny(nextMessage.Task, cancelTcs.Task).ConfigureAwait(false);
102  cancellationToken.ThrowIfCancellationRequested();
103  lock (messageQueue)
104  {
105  var result = messageQueue.Dequeue();
106  if (messageQueue.Count == 0)
107  nextMessage = new TaskCompletionSource<object>();
108  return result;
109  }
110  }
111 
117  {
118  lock (reconnectTaskLock)
119  if (reconnectCts != null)
120  {
121  reconnectCts.Cancel();
122  reconnectCts.Dispose();
123  reconnectCts = null;
124  Task reconnectTask = this.reconnectTask;
125  this.reconnectTask = null;
126  return reconnectTask;
127  }
128 
129  return Task.CompletedTask;
130  }
131 
133  public Task SetReconnectInterval(uint reconnectInterval)
134  {
135  if (reconnectInterval == 0)
136  throw new ArgumentOutOfRangeException(nameof(reconnectInterval), reconnectInterval, "Reconnect interval cannot be zero!");
137 
138  Task stopOldTimerTask;
139  lock (reconnectTaskLock)
140  {
141  stopOldTimerTask = StopReconnectionTimer();
142  reconnectCts = new CancellationTokenSource();
143  reconnectTask = ReconnectionLoop(reconnectInterval, reconnectCts.Token);
144  }
145 
146  return stopOldTimerTask;
147  }
148 
155  async Task ReconnectionLoop(uint reconnectInterval, CancellationToken cancellationToken)
156  {
157  do
158  {
159  try
160  {
161  await Task.Delay(TimeSpan.FromMinutes(reconnectInterval), cancellationToken).ConfigureAwait(false);
162  if (!Connected)
163  {
164  Logger.LogInformation("Attempting to reconnect provider...");
165  await Disconnect(cancellationToken).ConfigureAwait(false);
166  if (await Connect(cancellationToken).ConfigureAwait(false))
167  EnqueueMessage(null);
168  }
169  }
170  catch (OperationCanceledException)
171  {
172  break;
173  }
174  catch(Exception e)
175  {
176  Logger.LogError(e, "Error reconnecting!");
177  }
178  }
179  while (true);
180  }
181 
183  public abstract Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken);
184 
186  public abstract Task<Func<string, string, Task>> SendUpdateMessage(
187  RevisionInformation revisionInformation,
188  Version byondVersion,
189  DateTimeOffset? estimatedCompletionTime,
190  string gitHubOwner,
191  string gitHubRepo,
192  ulong channelId,
193  bool localCommitPushed,
194  CancellationToken cancellationToken);
195  }
196 }
readonly object reconnectTaskLock
Used for synchronizing access to reconnectCts and reconnectTask.
Definition: Provider.cs:26
CancellationTokenSource reconnectCts
CancellationTokenSource for reconnectTask
Definition: Provider.cs:41
Represents a message recieved by a IProvider
Definition: Message.cs:6
For interacting with a chat service
Definition: IProvider.cs:12
async Task< Message > NextMessage(CancellationToken cancellationToken)
Get a Task<TResult> resulting in the next Message the IProvider recieves or on a disconnect ...
Definition: Provider.cs:97
TaskCompletionSource< object > nextMessage
TaskCompletionSource<TResult> that completes while messageQueue isn&#39;t empty
Definition: Provider.cs:31
Task SetReconnectInterval(uint reconnectInterval)
Set the interval at which the provider tries to reconnect.
Definition: Provider.cs:133
Task StopReconnectionTimer()
Stops and awaits the reconnectTask.
Definition: Provider.cs:116
Provider(ILogger logger, uint reconnectInterval)
Construct a Provider
Definition: Provider.cs:48
readonly Queue< Message > messageQueue
Queue<T> of received Messages
Definition: Provider.cs:21
async Task ReconnectionLoop(uint reconnectInterval, CancellationToken cancellationToken)
Creates a Task that will attempt to reconnect the Provider every reconnectInterval minutes...
Definition: Provider.cs:155
void EnqueueMessage(Message message)
Queues a message for NextMessage(CancellationToken)
Definition: Provider.cs:71