Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 75 additions & 7 deletions OpenUtau.Core/Api/PhonemizerRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
private readonly CancellationTokenSource shutdown = new CancellationTokenSource();
private readonly BlockingCollection<PhonemizerRequest> requests = new BlockingCollection<PhonemizerRequest>();
private readonly object busyLock = new object();
private readonly object pendingLock = new object();
private int pendingRequests;
private Exception pendingException;
private List<TaskCompletionSource<object>> idleWaiters = new List<TaskCompletionSource<object>>();
Comment on lines +35 to +38
private Thread thread;

public PhonemizerRunner(TaskScheduler mainScheduler) {
Expand All @@ -44,7 +48,15 @@
}

public void Push(PhonemizerRequest request) {
requests.Add(request);
lock (pendingLock) {
pendingRequests++;
}
try {
requests.Add(request);
} catch {
CompleteRequest();
throw;
}
}

void PhonemizerLoop() {
Expand All @@ -60,7 +72,14 @@
}
for (int i = toRun.Count - 1; i >= 0; i--) {
if (parts.Remove(toRun[i].part)) {
SendResponse(Phonemize(toRun[i]));
try {
SendResponse(Phonemize(toRun[i]));
} catch (Exception e) {
Log.Error(e, "phonemizer request failed.");
CompleteRequest(e);
}
} else {
CompleteRequest();
}
}
parts.Clear();
Expand All @@ -73,7 +92,7 @@
}

void SendResponse(PhonemizerResponse response) {
Task.Factory.StartNew(_ => {
var task = Task.Factory.StartNew(_ => {
if (DocManager.Inst.Project.parts.Contains(response.part)) {
response.part.SetPhonemizerResponse(response);
}
Expand All @@ -84,6 +103,9 @@
});
DocManager.Inst.ExecuteCmd(new PhonemizedNotification());
}, null, CancellationToken.None, TaskCreationOptions.None, mainScheduler);
task.ContinueWith(t => {
CompleteRequest(t.IsFaulted ? t.Exception?.Flatten() : null);
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}

static PhonemizerResponse Phonemize(PhonemizerRequest request) {
Expand Down Expand Up @@ -205,11 +227,57 @@
/// Should only be used in command line mode.
/// </summary>
public void WaitFinish() {
while (true) {
lock (busyLock) {
if (requests.Count == 0) {
return;
WaitForIdleAsync().GetAwaiter().GetResult();
}

/// <summary>
/// Waits until all phonemizer requests queued before this call have
/// produced responses and those responses have been applied.
/// </summary>
public Task WaitForIdleAsync() {
lock (pendingLock) {
if (pendingRequests == 0) {
if (pendingException != null) {
var exception = pendingException;
pendingException = null;
return Task.FromException(exception);
}
Comment on lines +239 to 244
return Task.CompletedTask;
}
var waiter = new TaskCompletionSource<object>(
TaskCreationOptions.RunContinuationsAsynchronously);
idleWaiters.Add(waiter);
return waiter.Task;
}
}

void CompleteRequest(Exception exception = null) {

Check warning on line 254 in OpenUtau.Core/Api/PhonemizerRunner.cs

View workflow job for this annotation

GitHub Actions / pr-test (ubuntu-latest, linux-x64)

Cannot convert null literal to non-nullable reference type.

Check warning on line 254 in OpenUtau.Core/Api/PhonemizerRunner.cs

View workflow job for this annotation

GitHub Actions / pr-test (macos-latest, osx-arm64)

Cannot convert null literal to non-nullable reference type.

Check warning on line 254 in OpenUtau.Core/Api/PhonemizerRunner.cs

View workflow job for this annotation

GitHub Actions / pr-test (macos-15-intel, osx-x64)

Cannot convert null literal to non-nullable reference type.

Check warning on line 254 in OpenUtau.Core/Api/PhonemizerRunner.cs

View workflow job for this annotation

GitHub Actions / pr-test (windows-latest, win-x64)

Cannot convert null literal to non-nullable reference type.
List<TaskCompletionSource<object>> waiters = null;
Exception completedException = null;
lock (pendingLock) {
Comment on lines +254 to +257
if (exception != null && pendingException == null) {
pendingException = exception;
}
if (pendingRequests > 0) {
pendingRequests--;
}
if (pendingRequests == 0 && idleWaiters.Count > 0) {
completedException = pendingException;
pendingException = null;
waiters = idleWaiters;
idleWaiters = new List<TaskCompletionSource<object>>();
}
}
if (waiters == null) {
return;
}
if (completedException != null) {
foreach (var waiter in waiters) {
waiter.SetException(completedException);
}
} else {
foreach (var waiter in waiters) {
waiter.SetResult(null);
}
Comment on lines +277 to 281
}
}
Expand Down
150 changes: 150 additions & 0 deletions OpenUtau.Core/Headless/HeadlessOpenUtauHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OpenUtau.Classic;
using OpenUtau.Core.Util;
using Serilog;

namespace OpenUtau.Core.Headless {
public sealed class HeadlessOpenUtauHost : ICmdSubscriber, IDisposable {
private readonly HeadlessTaskScheduler scheduler;
private readonly SynchronizationContext? previousSynchronizationContext;
private readonly TextWriter? output;
private readonly List<string> errors = new List<string>();
private string lastProgressInfo = string.Empty;
private bool disposed;

public HeadlessOpenUtauHost(
HeadlessOpenUtauOptions? options = null,
TextWriter? output = null) {
this.output = output;
scheduler = new HeadlessTaskScheduler(Thread.CurrentThread);
previousSynchronizationContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(new HeadlessSynchronizationContext(scheduler));

if (!string.IsNullOrWhiteSpace(options?.SingersPath)) {
Preferences.Default.AdditionalSingerPath = Path.GetFullPath(options.SingersPath);
}
ApplyPreferenceOverrides(options);

Log.Information("Initializing OpenUtau headless host.");
ToolsManager.Inst.Initialize();
SingerManager.Inst.Initialize();
DocManager.Inst.Initialize(Thread.CurrentThread, scheduler);
DocManager.Inst.PostOnUIThread = scheduler.Post;
DocManager.Inst.AddSubscriber(this);
Log.Information("Initialized OpenUtau headless host.");
}

private static void ApplyPreferenceOverrides(HeadlessOpenUtauOptions? options) {
if (options == null) {
return;
}
if (!string.IsNullOrWhiteSpace(options.OnnxRunner)) {
Preferences.Default.OnnxRunner = options.OnnxRunner;
}
if (options.OnnxGpu.HasValue) {
Preferences.Default.OnnxGpu = options.OnnxGpu.Value;
}
if (options.DiffSingerDepth.HasValue) {
Preferences.Default.DiffSingerDepth = options.DiffSingerDepth.Value;
}
if (options.DiffSingerSteps.HasValue) {
Preferences.Default.DiffSingerSteps = options.DiffSingerSteps.Value;
}
if (options.DiffSingerVarianceSteps.HasValue) {
Preferences.Default.DiffSingerStepsVariance = options.DiffSingerVarianceSteps.Value;
}
if (options.DiffSingerPitchSteps.HasValue) {
Preferences.Default.DiffSingerStepsPitch = options.DiffSingerPitchSteps.Value;
}
if (options.DiffSingerTensorCache.HasValue) {
Preferences.Default.DiffSingerTensorCache = options.DiffSingerTensorCache.Value;
}
}

public T Run<T>(Func<Task<T>> operation) {
if (!scheduler.IsOwnerThread) {
throw new InvalidOperationException("Headless host must be run on its owner thread.");
}
Task<T> task;
try {
task = operation();
} catch (Exception e) {
task = Task.FromException<T>(e);
}
while (!task.IsCompleted) {
scheduler.RunOne(TimeSpan.FromMilliseconds(50));
}
scheduler.RunAvailable();
return task.GetAwaiter().GetResult();
}

public void ClearErrors() {
lock (errors) {
errors.Clear();
}
}

public string[] TakeErrors() {
lock (errors) {
var result = errors.ToArray();
errors.Clear();
return result;
}
}

public void OnNext(UCommand cmd, bool isUndo) {
if (cmd is ErrorMessageNotification error) {
lock (errors) {
errors.Add(FormatError(error));
}
} else if (cmd is ProgressBarNotification progress) {
PublishProgress(progress);
}
}

private void PublishProgress(ProgressBarNotification progress) {
if (output == null ||
progress.Progress != 0 ||
string.IsNullOrWhiteSpace(progress.Info) ||
progress.Info == lastProgressInfo) {
return;
}
lastProgressInfo = progress.Info;
output.WriteLine(progress.Info);
}

private static string FormatError(ErrorMessageNotification notification) {
if (notification.e is MessageCustomizableException mce) {
var message = string.IsNullOrWhiteSpace(mce.Message)
? mce.SubstanceException.Message
: mce.Message;
return string.IsNullOrWhiteSpace(mce.SubstanceException.Message)
? message
: $"{message}: {mce.SubstanceException.Message}";
}
if (!string.IsNullOrWhiteSpace(notification.message)) {
return notification.e == null
? notification.message
: $"{notification.message}: {notification.e.Message}";
}
return notification.e?.Message ?? notification.ToString();
}

public void Dispose() {
if (disposed) {
return;
}
disposed = true;
DocManager.Inst.RemoveSubscriber(this);
DocManager.Inst.PhonemizerRunner?.Dispose();
scheduler.RunAvailable();
SynchronizationContext.SetSynchronizationContext(previousSynchronizationContext);
scheduler.Dispose();
}
}
}
Loading
Loading