tgstation-server  4.4.0
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 
95  protected abstract Task DisconnectImpl(CancellationToken cancellationToken);
96 
98  public async Task Disconnect(CancellationToken cancellationToken)
99  {
100  await StopReconnectionTimer().ConfigureAwait(false);
101  await DisconnectImpl(cancellationToken).ConfigureAwait(false);
102  }
103 
105  public abstract Task<IReadOnlyCollection<ChannelRepresentation>> MapChannels(IEnumerable<Api.Models.ChatChannel> channels, CancellationToken cancellationToken);
106 
108  public async Task<Message> NextMessage(CancellationToken cancellationToken)
109  {
110  var cancelTcs = new TaskCompletionSource<object>();
111  using (cancellationToken.Register(() => cancelTcs.SetCanceled()))
112  await Task.WhenAny(nextMessage.Task, cancelTcs.Task).ConfigureAwait(false);
113  cancellationToken.ThrowIfCancellationRequested();
114  lock (messageQueue)
115  {
116  var result = messageQueue.Dequeue();
117  if (messageQueue.Count == 0)
118  nextMessage = new TaskCompletionSource<object>();
119  return result;
120  }
121  }
122 
128  {
129  lock (reconnectTaskLock)
130  if (reconnectCts != null)
131  {
132  reconnectCts.Cancel();
133  reconnectCts.Dispose();
134  reconnectCts = null;
135  Task reconnectTask = this.reconnectTask;
136  this.reconnectTask = null;
137  return reconnectTask;
138  }
139 
140  return Task.CompletedTask;
141  }
142 
144  public Task SetReconnectInterval(uint reconnectInterval)
145  {
146  if (reconnectInterval == 0)
147  throw new ArgumentOutOfRangeException(nameof(reconnectInterval), reconnectInterval, "Reconnect interval cannot be zero!");
148 
149  Task stopOldTimerTask;
150  lock (reconnectTaskLock)
151  {
152  stopOldTimerTask = StopReconnectionTimer();
153  reconnectCts = new CancellationTokenSource();
154  reconnectTask = ReconnectionLoop(reconnectInterval, reconnectCts.Token);
155  }
156 
157  return stopOldTimerTask;
158  }
159 
166  async Task ReconnectionLoop(uint reconnectInterval, CancellationToken cancellationToken)
167  {
168  do
169  {
170  try
171  {
172  await Task.Delay(TimeSpan.FromMinutes(reconnectInterval), cancellationToken).ConfigureAwait(false);
173  if (!Connected)
174  {
175  Logger.LogInformation("Attempting to reconnect provider...");
176  await Disconnect(cancellationToken).ConfigureAwait(false);
177  if (await Connect(cancellationToken).ConfigureAwait(false))
178  EnqueueMessage(null);
179  }
180  }
181  catch (OperationCanceledException)
182  {
183  break;
184  }
185  catch(Exception e)
186  {
187  Logger.LogError(e, "Error reconnecting!");
188  }
189  }
190  while (true);
191  }
192 
194  public abstract Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken);
195 
197  public abstract Task<Func<string, string, Task>> SendUpdateMessage(
198  RevisionInformation revisionInformation,
199  Version byondVersion,
200  DateTimeOffset? estimatedCompletionTime,
201  string gitHubOwner,
202  string gitHubRepo,
203  ulong channelId,
204  bool localCommitPushed,
205  CancellationToken cancellationToken);
206  }
207 }
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 Disconnect(CancellationToken cancellationToken)
Gracefully disconnects the provider. Permanently stops the reconnection timer.
Definition: Provider.cs:98
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:108
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:144
Task StopReconnectionTimer()
Stops and awaits the reconnectTask.
Definition: Provider.cs:127
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:166
void EnqueueMessage(Message message)
Queues a message for NextMessage(CancellationToken)
Definition: Provider.cs:71