Skip to content

Commit a720bb2

Browse files
committed
All async scheduling done
1 parent 268d79c commit a720bb2

30 files changed

+605
-466
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Download either one of the latest builds from our [nightly server](https://splam
5050
#### Linux
5151
1. dotnet core: Get the latest `dotnet core 3.1` version by following [this tutorial](https://dotnet.microsoft.com/download/linux-package-manager/ubuntu16-04/sdk-current) and follow the steps after choosing your platform
5252
1. Other dependencies:
53-
* on **Ubuntu**:
53+
* on **Ubuntu**/**Debian**:
5454
Run `sudo apt-get install libopus-dev ffmpeg`
5555
* on **Arch Linux**:
5656
Run `sudo pacman -S opus ffmpeg`

TS3AudioBot/Bot.cs

+50-56
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public sealed class Bot
4141
private readonly ConfBot config;
4242
private readonly TickWorker idleTickWorker;
4343

44-
internal bool IsDisposed { get; private set; }
44+
private bool isClosed;
4545
internal BotInjector Injector { get; }
4646
internal DedicatedTaskScheduler Scheduler { get; }
4747

@@ -76,15 +76,17 @@ public Bot(Id id, ConfBot config, BotInjector injector)
7676
config.Events.OnIdle.Changed += (s, e) => EnableIdleTickWorker();
7777

7878
var builder = new DependencyBuilder(Injector);
79-
builder.AddModule(this);
80-
builder.AddModule(config);
81-
builder.AddModule(Injector);
82-
builder.AddModule(config.Playlists);
79+
Injector.HideParentModule<CommandManager>();
80+
Injector.HideParentModule<DedicatedTaskScheduler>();
81+
Injector.AddModule(this);
82+
Injector.AddModule(config);
83+
Injector.AddModule(Injector);
84+
Injector.AddModule(config.Playlists);
85+
Injector.AddModule(config.History);
86+
Injector.AddModule(Id);
8387
builder.RequestModule<PlaylistIO>();
8488
builder.RequestModule<PlaylistManager>();
85-
builder.AddModule(Id);
8689
builder.RequestModule<DedicatedTaskScheduler>();
87-
builder.RequestModule<TaskScheduler, DedicatedTaskScheduler>();
8890
builder.RequestModule<TsFullClient>();
8991
builder.RequestModule<TsBaseFunctions, TsFullClient>();
9092
builder.RequestModule<Ts3Client>();
@@ -93,12 +95,9 @@ public Bot(Id id, ConfBot config, BotInjector injector)
9395
builder.RequestModule<IVoiceTarget, CustomTargetPipe>();
9496
builder.RequestModule<SessionManager>();
9597
builder.RequestModule<ResolveContext>();
96-
if (!builder.TryCreate<CommandManager>(out var commandManager))
97-
throw new Exception("Failed to create commandManager");
98-
builder.AddModule(commandManager);
98+
builder.RequestModule<CommandManager>();
9999
if (config.History.Enabled)
100100
{
101-
builder.AddModule(config.History);
102101
builder.RequestModule<HistoryManager>();
103102
}
104103
builder.RequestModule<PlayManager>();
@@ -108,6 +107,7 @@ public Bot(Id id, ConfBot config, BotInjector injector)
108107
Log.Error("Missing bot module dependency");
109108
throw new Exception("Could not load all bot modules");
110109
}
110+
Injector.ClearHiddenParentModules();
111111

112112
resourceResolver = Injector.GetModuleOrThrow<ResolveContext>();
113113
ts3FullClient = Injector.GetModuleOrThrow<TsFullClient>();
@@ -121,6 +121,7 @@ public Bot(Id id, ConfBot config, BotInjector injector)
121121
targetManager = Injector.GetModuleOrThrow<IVoiceTarget>();
122122
sessionManager = Injector.GetModuleOrThrow<SessionManager>();
123123
stats = Injector.GetModuleOrThrow<Stats>();
124+
var commandManager = Injector.GetModuleOrThrow<CommandManager>();
124125

125126
idleTickWorker = Scheduler.Invoke(() => Scheduler.CreateTimer(OnIdle, TimeSpan.MaxValue, false)).Result;
126127

@@ -152,10 +153,12 @@ public Bot(Id id, ConfBot config, BotInjector injector)
152153
ts3client.OnMessageReceived += OnMessageReceived;
153154
// Register callback to remove open private sessions, when user disconnects
154155
ts3FullClient.OnEachClientLeftView += OnClientLeftView;
155-
ts3client.OnBotDisconnect += OnBotDisconnect;
156+
ts3client.OnBotConnected += OnBotConnected;
157+
ts3client.OnBotDisconnected += OnBotDisconnected;
158+
ts3client.OnBotStoppedReconnecting += OnBotStoppedReconnecting;
156159
// Alone mode
157160
ts3client.OnAloneChanged += OnAloneChanged;
158-
ts3client.OnAloneChanged += (s, e) => customTarget.Alone = e.Alone;
161+
ts3client.OnAloneChanged += (s, e) => { customTarget.Alone = e.Alone; return Task.CompletedTask; };
159162
// Whisper stall
160163
ts3client.OnWhisperNoTarget += (s, e) => player.SetStall();
161164

@@ -170,36 +173,21 @@ public Bot(Id id, ConfBot config, BotInjector injector)
170173
commandManager.RegisterAlias(alias.Key, alias.Value).UnwrapToLog(Log);
171174
}
172175

173-
public async Task<E<string>> Run()
176+
public Task<E<string>> Run()
174177
{
175-
Log.Info("Bot \"{0}\" connecting to \"{1}\"", config.Name, config.Connect.Address);
176-
var result = await ts3client.Connect();
177-
if (!result.Ok)
178-
return result;
179-
180-
EnableIdleTickWorker();
181-
182-
var badges = config.Connect.Badges.Value;
183-
if (!string.IsNullOrEmpty(badges))
184-
ts3client?.ChangeBadges(badges);
178+
Scheduler.VerifyOwnThread();
185179

186-
var onStart = config.Events.OnConnect.Value;
187-
if (!string.IsNullOrEmpty(onStart))
188-
{
189-
var info = CreateExecInfo();
190-
await CallScript(info, onStart, false, true);
191-
}
192-
193-
return R.Ok;
180+
Log.Info("Bot \"{0}\" connecting to \"{1}\"", config.Name, config.Connect.Address);
181+
return Task.FromResult(ts3client.Connect());
194182
}
195183

196184
public async Task Stop()
197185
{
198-
ValidateScheduler();
186+
Scheduler.VerifyOwnThread();
199187

200188
Injector.GetModule<BotManager>()?.RemoveBot(this);
201189

202-
if (!IsDisposed) IsDisposed = true;
190+
if (!isClosed) isClosed = true;
203191
else return;
204192

205193
Log.Info("Bot ({0}) disconnecting.", Id);
@@ -216,7 +204,23 @@ public async Task Stop()
216204
config.ClearEvents();
217205
}
218206

219-
private async void OnBotDisconnect(object? sender, DisconnectEventArgs e)
207+
private async Task OnBotConnected(object? sender, EventArgs e)
208+
{
209+
EnableIdleTickWorker();
210+
211+
var badges = config.Connect.Badges.Value;
212+
if (!string.IsNullOrEmpty(badges))
213+
ts3client?.ChangeBadges(badges);
214+
215+
var onStart = config.Events.OnConnect.Value;
216+
if (!string.IsNullOrEmpty(onStart))
217+
{
218+
var info = CreateExecInfo();
219+
await CallScript(info, onStart, false, true);
220+
}
221+
}
222+
223+
private async Task OnBotDisconnected(object? sender, DisconnectEventArgs e)
220224
{
221225
DisableIdleTickWorker();
222226

@@ -226,11 +230,14 @@ private async void OnBotDisconnect(object? sender, DisconnectEventArgs e)
226230
var info = CreateExecInfo();
227231
await CallScript(info, onStop, false, true);
228232
}
233+
}
229234

230-
await Stop();
235+
private Task OnBotStoppedReconnecting(object? sender, EventArgs e)
236+
{
237+
return Stop();
231238
}
232239

233-
private async void OnMessageReceived(object? sender, TextMessage textMessage)
240+
private async Task OnMessageReceived(object? sender, TextMessage textMessage)
234241
{
235242
if (textMessage?.Message == null)
236243
{
@@ -332,9 +339,9 @@ public Task UpdateBotStatus()
332339

333340
public async Task UpdateBotStatusInternal()
334341
{
335-
ValidateScheduler();
342+
Scheduler.VerifyOwnThread();
336343

337-
if (IsDisposed)
344+
if (isClosed)
338345
return;
339346

340347
if (!config.SetStatusDescription)
@@ -362,9 +369,9 @@ public Task GenerateStatusImage(bool playing, PlayInfoEventArgs? startEvent)
362369

363370
public async Task GenerateStatusImageInternal(bool playing, PlayInfoEventArgs? startEvent)
364371
{
365-
ValidateScheduler();
372+
Scheduler.VerifyOwnThread();
366373

367-
if (!config.GenerateStatusAvatar || IsDisposed)
374+
if (!config.GenerateStatusAvatar || isClosed)
368375
return;
369376

370377
static Stream? GetRandomFile(string? basePath, string prefix)
@@ -524,9 +531,9 @@ private void EnableIdleTickWorker()
524531

525532
#region Event: Alone/Party
526533

527-
private async void OnAloneChanged(object? sender, AloneChanged e)
534+
private async Task OnAloneChanged(object? sender, AloneChanged e)
528535
{
529-
ValidateScheduler();
536+
Scheduler.VerifyOwnThread();
530537

531538
string script;
532539
TimeSpan delay;
@@ -579,19 +586,6 @@ await info.Write(TextMod.Format(config.Commands.Color, strings.error_call_unexpe
579586
}
580587
}
581588

582-
private void ValidateScheduler()
583-
{
584-
if (TaskScheduler.Current != Scheduler)
585-
{
586-
var stack = new System.Diagnostics.StackTrace();
587-
Log.Error("Current call is not scheduled correctly. Sched: {0}, Own: {1}. Stack: {2}",
588-
TaskScheduler.Current.Id,
589-
Scheduler.Id,
590-
stack
591-
);
592-
}
593-
}
594-
595589
public BotInfo GetInfo() => new BotInfo
596590
{
597591
Id = Id,

TS3AudioBot/BotManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public async Task<R<BotInfo, string>> RunBot(ConfBot config)
132132
if (!initializeResult.Ok)
133133
{
134134
await StopBot(bot);
135-
return $"Bot failed to connect ({initializeResult.Error})";
135+
return $"Bot failed to initialize ({initializeResult.Error})";
136136
}
137137
return bot.GetInfo();
138138
});

TS3AudioBot/Config/ConfigStructs.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ public static class ConfTimeExtensions
342342
var value = conf.Value;
343343
if (value.Count == 0)
344344
return null;
345-
var last = value[value.Count - 1];
345+
var last = value[^1];
346346
var repeat = last == "repeat" || last == "repeat last"; // "repeat" might get removed for other loops, but for now keep as hidden alternative
347347
var max = repeat ? value.Count - 2 : value.Count - 1;
348348
if (index <= max)
@@ -355,7 +355,7 @@ public static E<string> ValidateTime(IReadOnlyList<string> value)
355355
{
356356
if (value.Count == 0)
357357
return R.Ok;
358-
var last = value[value.Count - 1];
358+
var last = value[^1];
359359
var repeat = last == "repeat" || last == "repeat last";
360360
if (repeat && value.Count == 1)
361361
return $"Specified 'repeat' without any previous value.";

TS3AudioBot/Core.cs

+35-28
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using TS3AudioBot.Rights;
2121
using TS3AudioBot.Sessions;
2222
using TS3AudioBot.Web;
23+
using TSLib.Scheduler;
2324

2425
namespace TS3AudioBot
2526
{
@@ -28,71 +29,72 @@ public sealed class Core
2829
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
2930
private readonly string configFilePath;
3031
private bool forceNextExit;
32+
private readonly DedicatedTaskScheduler scheduler;
3133
private readonly CoreInjector injector;
3234

33-
public Core(string? configFilePath = null)
35+
public Core(DedicatedTaskScheduler scheduler, string? configFilePath = null)
3436
{
37+
this.scheduler = scheduler;
3538
// setting defaults
3639
this.configFilePath = configFilePath ?? FilesConst.CoreConfig;
3740

3841
injector = new CoreInjector();
3942
}
4043

41-
public async Task<E<string>> Run(ParameterData setup)
44+
public async Task Run(ParameterData setup)
4245
{
43-
AppDomain.CurrentDomain.UnhandledException += ExceptionHandler;
46+
scheduler.VerifyOwnThread();
47+
48+
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
4449
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
4550
Console.CancelKeyPress += ConsoleInterruptHandler;
4651

4752
var config = ConfRoot.OpenOrCreate(configFilePath);
4853
if (config is null)
49-
return "Could not create config";
54+
throw new Exception("Could not create config");
5055
ConfigUpgrade2.Upgrade(config.Configs.BotsPath.Value);
5156
config.Save();
5257

5358
var builder = new DependencyBuilder(injector);
5459

55-
builder.AddModule(this);
56-
builder.AddModule(config);
57-
builder.AddModule(injector);
58-
builder.AddModule(config.Db);
60+
injector.AddModule(this);
61+
injector.AddModule(scheduler);
62+
injector.AddModule(injector);
63+
injector.AddModule(config);
64+
injector.AddModule(config.Db);
65+
injector.AddModule(config.Plugins);
66+
injector.AddModule(config.Web);
67+
injector.AddModule(config.Web.Interface);
68+
injector.AddModule(config.Web.Api);
69+
injector.AddModule(config.Rights);
70+
injector.AddModule(config.Factories);
5971
builder.RequestModule<SystemMonitor>();
6072
builder.RequestModule<DbStore>();
61-
builder.AddModule(config.Plugins);
6273
builder.RequestModule<PluginManager>();
63-
builder.AddModule(config.Web);
64-
builder.AddModule(config.Web.Interface);
65-
builder.AddModule(config.Web.Api);
6674
builder.RequestModule<WebServer>();
67-
builder.AddModule(config.Rights);
6875
builder.RequestModule<RightsManager>();
6976
builder.RequestModule<BotManager>();
7077
builder.RequestModule<TokenManager>();
7178
builder.RequestModule<CommandManager>();
72-
builder.AddModule(config.Factories);
7379
builder.RequestModule<ResourceResolver>();
7480
builder.RequestModule<Stats>();
7581

7682
if (!builder.Build())
77-
return "Could not load all core modules";
83+
throw new Exception("Could not load all core modules");
7884

7985
YoutubeDlHelper.DataObj = config.Tools.YoutubeDl;
8086

81-
builder.GetModuleOrThrow<SystemMonitor>().StartTimedSnapshots();
82-
builder.GetModuleOrThrow<CommandManager>().RegisterCollection(MainCommands.Bag);
83-
builder.GetModuleOrThrow<RightsManager>().CreateConfigIfNotExists(setup.Interactive);
84-
builder.GetModuleOrThrow<WebServer>().StartWebServer();
85-
builder.GetModuleOrThrow<Stats>().StartTimer(setup.SendStats);
86-
await builder.GetModuleOrThrow<BotManager>().RunBots(setup.Interactive);
87-
88-
return R.Ok;
87+
injector.GetModuleOrThrow<CommandManager>().RegisterCollection(MainCommands.Bag);
88+
injector.GetModuleOrThrow<RightsManager>().CreateConfigIfNotExists(setup.Interactive);
89+
injector.GetModuleOrThrow<WebServer>().StartWebServer();
90+
injector.GetModuleOrThrow<Stats>().StartTimer(setup.SendStats);
91+
await injector.GetModuleOrThrow<BotManager>().RunBots(setup.Interactive);
8992
}
9093

91-
public void ExceptionHandler(object sender, UnhandledExceptionEventArgs e)
94+
public void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
9295
{
9396
Log.Fatal(e.ExceptionObject as Exception, "Critical program failure!");
94-
Stop().Wait();
95-
System.Environment.Exit(-1);
97+
StopAsync().RunSynchronously();
9698
}
9799

98100
public static void UnobservedTaskExceptionHandler(object? sender, UnobservedTaskExceptionEventArgs e)
@@ -109,7 +111,7 @@ public void ConsoleInterruptHandler(object sender, ConsoleCancelEventArgs e)
109111
Log.Info("Got interrupt signal, trying to soft-exit.");
110112
e.Cancel = true;
111113
forceNextExit = true;
112-
Stop().Wait();
114+
Stop();
113115
}
114116
else
115117
{
@@ -119,7 +121,9 @@ public void ConsoleInterruptHandler(object sender, ConsoleCancelEventArgs e)
119121
}
120122
}
121123

122-
public async Task Stop()
124+
public void Stop() => _ = scheduler.InvokeAsync(StopAsync);
125+
126+
private async Task StopAsync()
123127
{
124128
Log.Info("TS3AudioBot shutting down.");
125129

@@ -130,6 +134,9 @@ public async Task Stop()
130134
injector.GetModule<WebServer>()?.Dispose();
131135
injector.GetModule<DbStore>()?.Dispose();
132136
injector.GetModule<ResourceResolver>()?.Dispose();
137+
injector.GetModule<DedicatedTaskScheduler>()?.Dispose();
138+
139+
Log.Info("Bye");
133140
}
134141
}
135142
}

0 commit comments

Comments
 (0)