From 97b09ed26df13840088c6d8a69b70af3b5e0e6b6 Mon Sep 17 00:00:00 2001 From: GermanCoding Date: Sat, 18 Jul 2026 14:47:23 +0200 Subject: [PATCH] feat: Refuse start when CEF cache is locked We frequently see that users have conflicting SyncTrayzor processes on the system, but in different locations. SyncTrayzor's IPC only triggers when processes are using the same install - but for whatever reasons some users have different SyncTrayzor processes in multiple locations using shared configurations. This can't work properly - the CEF cache is locked exclusively by the first process. This then causes a CEF init failure, with a fairly generic error message that doesn't explain what's going on. Try to detect such a scenario by locking the file first - if that fails, we know another process already has that config. Explain the problem to the user and refuse startup in such a case. --- src/SyncTrayzor/Pages/ViewerViewModel.cs | 116 ++++++++++++++-------- src/SyncTrayzor/Properties/Resources.resx | 8 ++ 2 files changed, 80 insertions(+), 44 deletions(-) diff --git a/src/SyncTrayzor/Pages/ViewerViewModel.cs b/src/SyncTrayzor/Pages/ViewerViewModel.cs index 24bbd357..399d6275 100644 --- a/src/SyncTrayzor/Pages/ViewerViewModel.cs +++ b/src/SyncTrayzor/Pages/ViewerViewModel.cs @@ -7,20 +7,22 @@ using CefSharp; using CefSharp.Wpf; using SyncTrayzor.Services.Config; +using System.IO; using System.Threading; -using System.Threading.Tasks; using System.Windows; using SyncTrayzor.Services; using SyncTrayzor.Properties; using Microsoft.WindowsAPICodePack.Dialogs; using CefSharp.Handler; -using PropertyChanged; +using SyncTrayzor.Localization; namespace SyncTrayzor.Pages { public class ViewerViewModel : Screen, IResourceRequestHandlerFactory, ILifeSpanHandler, IContextMenuHandler, IDisposable { + private const int ErrorSharingViolation = unchecked((int)0x80070020); + private readonly IWindowManager windowManager; private readonly ISyncthingManager syncthingManager; private readonly IProcessStartProvider processStartProvider; @@ -30,6 +32,7 @@ public class ViewerViewModel : Screen, IResourceRequestHandlerFactory, ILifeSpan private readonly CustomResourceRequestHandler customResourceRequestHandler; private readonly object cultureLock = new(); // This can be read from many threads + private readonly Lock _initLock = new(); private CultureInfo? culture; private double zoomLevel; @@ -109,58 +112,83 @@ protected override void OnInitialActivate() language = culture!.Name; } - var cefLogFile = pathsProvider.LogFilePath + @"\cef.log"; - var settings = new CefSettings() + lock (_initLock) { - RemoteDebuggingPort = AppSettings.Instance.CefRemoteDebuggingPort, - // We really only want to set the LocalStorage path, but we don't have that level of control.... - CachePath = pathsProvider.CefCachePath, - LogFile = cefLogFile, - IgnoreCertificateErrors = true, - LogSeverity = LogSeverity.Warning, - AcceptLanguageList = language, - Locale = language - }; - - // CEF will attempt de-elevating the process when launched as admin. This is done by relaunching - // the current process in unprivileged mode. However, we're not equipped to handle such a - // process restart cleanly, so ask CEF to not relaunch. Running as admin is not ideal for security, - // but the user wants it... - settings.CefCommandLineArgs.Add("do-not-de-elevate"); - - // System proxy settings (which also specify a proxy for localhost) shouldn't affect us - settings.CefCommandLineArgs.Add("no-proxy-server", "1"); - settings.CefCommandLineArgs.Add("disable-cache", "1"); - settings.CefCommandLineArgs.Add("disable-extensions", "1"); - - if (configuration.DisableHardwareRendering) - { - settings.CefCommandLineArgs.Add("disable-gpu"); - settings.CefCommandLineArgs.Add("disable-gpu-vsync"); + // It may happen that we have another SyncTrazyor instance using the same CEF cache path + // This will cause a generic CEF initialization failure. Try to catch this early + // by acquiring the log file. + var cefCachePath = pathsProvider.CefCachePath; + var cefLock = cefCachePath + @"\lockfile"; try { - settings.CefCommandLineArgs.Add("disable-gpu-compositing"); + using var lockFile = new FileStream(cefLock, FileMode.OpenOrCreate, FileAccess.ReadWrite, + FileShare.None); + } + catch (IOException e) when (e.HResult == ErrorSharingViolation) + { + var msg = Localizer.Translate("ViewerView_CefCacheLocked", cefCachePath); + var title = Localizer.Translate("ViewerView_CefCacheLocked_Title"); + windowManager.ShowMessageBox(msg, title, MessageBoxButton.OK, MessageBoxImage.Error); + Environment.Exit(1); } - catch (ArgumentException) + catch (Exception) { - // CefSharp 139+ may set this by default, ignore if unable to set the flag. + // Ignore potentially unrelated errors } - settings.CefCommandLineArgs.Add("disable-application-cache"); - } + var cefLogFile = pathsProvider.LogFilePath + @"\cef.log"; + var settings = new CefSettings() + { + RemoteDebuggingPort = AppSettings.Instance.CefRemoteDebuggingPort, + // We really only want to set the LocalStorage path, but we don't have that level of control.... + CachePath = cefCachePath, + LogFile = cefLogFile, + IgnoreCertificateErrors = true, + LogSeverity = LogSeverity.Warning, + AcceptLanguageList = language, + Locale = language + }; + + // CEF will attempt de-elevating the process when launched as admin. This is done by relaunching + // the current process in unprivileged mode. However, we're not equipped to handle such a + // process restart cleanly, so ask CEF to not relaunch. Running as admin is not ideal for security, + // but the user wants it... + settings.CefCommandLineArgs.Add("do-not-de-elevate"); + + // System proxy settings (which also specify a proxy for localhost) shouldn't affect us + settings.CefCommandLineArgs.Add("no-proxy-server", "1"); + settings.CefCommandLineArgs.Add("disable-cache", "1"); + settings.CefCommandLineArgs.Add("disable-extensions", "1"); + + if (configuration.DisableHardwareRendering) + { + settings.CefCommandLineArgs.Add("disable-gpu"); + settings.CefCommandLineArgs.Add("disable-gpu-vsync"); + try + { + settings.CefCommandLineArgs.Add("disable-gpu-compositing"); + } + catch (ArgumentException) + { + // CefSharp 139+ may set this by default, ignore if unable to set the flag. + } + + settings.CefCommandLineArgs.Add("disable-application-cache"); + } - try - { - if (!Cef.Initialize(settings)) + try { - throw new InvalidOperationException("Unable to initialize CEF."); + if (!Cef.Initialize(settings)) + { + throw new InvalidOperationException("Unable to initialize CEF."); + } + } + catch (InvalidOperationException e) + { + throw new AggregateException( + $"SyncTrayzor was unable to initialize its embedded browser.\nWhen reporting this issue, please attach the CEF logfile @ \n{cefLogFile}\n in addition to this message and the normal SyncTrayzor logfile.", + e); } - } - catch (InvalidOperationException e) - { - throw new AggregateException( - $"SyncTrayzor was unable to initialize its embedded browser.\nWhen reporting this issue, please attach the CEF logfile @ \n{cefLogFile}\n in addition to this message and the normal SyncTrayzor logfile.", - e); } } diff --git a/src/SyncTrayzor/Properties/Resources.resx b/src/SyncTrayzor/Properties/Resources.resx index 5abf94ff..b9d3c241 100644 --- a/src/SyncTrayzor/Properties/Resources.resx +++ b/src/SyncTrayzor/Properties/Resources.resx @@ -1014,4 +1014,12 @@ Please donate to the syncthing project. [File Deleted] When the base file for a conflict is not found, this placeholder is shown instead of the actual file name + + SyncTrayzor detected that another instance is currently running using the same configuration. Please stop any other SyncTrayzor instances, or restart your system to close them. + Shown when the embedded browser cannot acquire exclusive access to its cache folder, because another SyncTrayzor instance is running in a different location. + + + Conflicting SyncTrayzor processes detected + Title of the window when the ViewerView_CefCacheLocked message is displayed +