Skip to content

Commit

Permalink
MultipleInstancesDemo - segregating windows in separate app domains
Browse files Browse the repository at this point in the history
  • Loading branch information
stanislav-peichev committed Jun 28, 2024
1 parent 539c3d1 commit 1ec96c7
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 19 deletions.
129 changes: 118 additions & 11 deletions multi-instances/MultipleInstancesDemo/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Windows;
using DOT.AGM.GwTransport;
using DOT.Logging;
using log4net;
using log4net.Config;
using Tick42.StartingContext;

Expand All @@ -19,25 +20,25 @@ public partial class App : Application
private const string MutexName = "MultipleInstancesDemo_SingleInstanceMutex";
private const string PipeName = "MultipleInstancesDemo_Pipe";

private int instance_;
private volatile int alive_;

private Mutex mutex_;
private Thread pipeServerThread_;

private GwProtocolSerializer serializer_;


protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
};
AppDomain.CurrentDomain.UnhandledException += (sender, args) => { };
Debugger.Launch();
XmlConfigurator.Configure();
DotLoggingFacade.UseLibrary(LogLibrary.DynamicLog4Net);
serializer_ = new GwProtocolSerializer(ValueGwConverter.Settings.None);

// or choose another ShutdownMode if needed
ShutdownMode = ShutdownMode.OnLastWindowClose;
ShutdownMode = ShutdownMode.OnExplicitShutdown;

mutex_ = new Mutex(true, MutexName, out bool isOwned);

Expand Down Expand Up @@ -131,14 +132,120 @@ private void SendArgumentsToFirstInstance(string[] args, RemoteConfigurationOpti

private void ProcessArguments(string[] args, RemoteConfigurationOptions rco)
{
// Handle the arguments as needed in your application
Dispatcher.Invoke(() =>
// isolate the window in a new AppDomain, so it has its own log file

var setup = new AppDomainSetup
{
// Handle the arguments in the UI thread
// For example, pass the arguments to a new instance of the MainWindow
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
PrivateBinPath = "bin" // Ensure the private bin path is set if necessary
};

var newDomain = AppDomain.CreateDomain(rco?.InstanceId ?? "Main", null, setup);
newDomain.Load(typeof(LogManager).Assembly.FullName);

var runnerType = typeof(Isolator);
var runner = (Isolator)newDomain.CreateInstanceAndUnwrap(runnerType.Assembly.FullName, runnerType.FullName);

// Run the window in the new AppDomain
Dispatcher.BeginInvoke((Action)(() =>
{
Interlocked.Increment(ref alive_);
runner.RunIsolated(args, new SerializableRCO(rco), ++instance_);
// Optionally unload the new AppDomain when done
AppDomain.Unload(newDomain);
if (Interlocked.Decrement(ref alive_) == 0)
{
Shutdown();
}
}));


// or handle the arguments as needed in your application
// and create the window in the same AppDomain - this will share the log file with the main instance
// Dispatcher.Invoke(() =>
// {
// // Handle the arguments in the UI thread
// // For example, pass the arguments to a new instance of the MainWindow
//
// var window = new MainWindow();
//
// window.ProcessArguments(args, rco);
// });
}

[Serializable]
public class SerializableRCO
{
public SerializableRCO(RemoteConfigurationOptions rco)
{
if (rco == null)
{
return;
}

AppName = rco.AppName;
InstanceId = rco.InstanceId;
Environment = rco.Environment;
Region = rco.Region;
GwUrl = rco.GwUrl;
GwToken = rco.GwToken;
Username = rco.Username;
}

public string Environment { get; set; }
public string Region { get; set; }
public string GwUrl { get; set; }
public string GwToken { get; set; }
public string AppName { get; set; }
public string InstanceId { get; set; }
public string Username { get; set; }

public RemoteConfigurationOptions ToRCO()
{
return new RemoteConfigurationOptions
{
AppName = AppName,
InstanceId = InstanceId,
Environment = Environment,
Region = Region,
GwUrl = GwUrl,
GwToken = GwToken,
Username = Username
};
}
}

public class Isolator : MarshalByRefObject
{
public void RunIsolated(string[] args, SerializableRCO rco, int instance)
{
ConfigureLog4Net(rco?.InstanceId ?? "main");
DotLoggingFacade.UseLibrary(LogLibrary.DynamicLog4Net);
var app = new Application();
var window = new MainWindow();
window.ProcessArguments(args, rco);
});
window.ProcessArguments(args, rco?.ToRCO(), instance);
app.Run(window);
}

private void ConfigureLog4Net(string instance)
{
// Load log4net configuration from a file or create programmatically
Environment.SetEnvironmentVariable("instance_name", instance);
//var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
string logConfigFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config");
XmlConfigurator.Configure(new FileInfo(logConfigFilePath));

// Set a different log file for each AppDomain
// foreach (var appender in log4net.LogManager.GetRepository().GetAppenders())
// {
// if (appender.Name == "RollingFileAppender" && appender is RollingFileAppender fileAppender)
// {
// string logFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{instance}.log");
// fileAppender.File = logFile;
// fileAppender.ActivateOptions(); // Refresh the appender configuration
// }
// }
}
}
}
}
25 changes: 20 additions & 5 deletions multi-instances/MultipleInstancesDemo/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected override void OnClosed(EventArgs e)
}
}

public async void ProcessArguments(string[] args, RemoteConfigurationOptions rco)
public async void ProcessArguments(string[] args, RemoteConfigurationOptions rco, int instance)
{
var initializeOptions = new InitializeOptions
{
Expand All @@ -89,10 +89,20 @@ public async void ProcessArguments(string[] args, RemoteConfigurationOptions rco

var initializing = Glue42.InitializeGlue(initializeOptions);
glue_ = await initializing;
wnd_ = await glue_.GlueWindows.RegisterStartupWindow(this, initializeOptions.ApplicationName,
w => w.WithChannelSupport(true));
glue_.Interop.RegisterService<ISomethingService>(this,
modifyServiceConfig: c => c.Dispatcher = new WrappedDispatcher(Dispatcher));
bool somethingService = instance % 2 != 0;
string title = initializeOptions.ApplicationName + " " + instance + (somethingService
? " SOMETHING"
: " NO SERVICE");

wnd_ = await glue_.GlueWindows.RegisterStartupWindow(this, title,
w => w.WithChannelSupport(true).WithTitle(title));

if (somethingService)
{
glue_.Interop.RegisterService<ISomethingService>(this,
modifyServiceConfig: c => c.Dispatcher = new WrappedDispatcher(Dispatcher));
}

caller_ = glue_.Interop.CreateServiceProxy<ISomethingService>();

T GetRestoreState<T>(T _) => glue_.GetRestoreState<T>();
Expand All @@ -115,6 +125,11 @@ public async void ProcessArguments(string[] args, RemoteConfigurationOptions rco

private void DoSomething_Click(object sender, RoutedEventArgs e)
{
// to invoke it from JS you can just go:
// glue.interop.invoke('io.multiple.GetSomethingElse', {something: {thing: 'frog', price: 3.14, happenedOn: new Date()}}, "all");

// this will invoke the service method on the first instance
// and will add the result to the list
caller_.GetSomethingElse(new Something
{
Thing = "Something",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ioConnectNET, Version=1.10.0.0, Culture=neutral, PublicKeyToken=dbb353f1544d18d3, processorArchitecture=MSIL">
<HintPath>packages\io.Connect.NET.1.10.0.0\lib\net45\ioConnectNET.dll</HintPath>
<Reference Include="ioConnectNET, Version=1.12.0.0, Culture=neutral, PublicKeyToken=dbb353f1544d18d3, processorArchitecture=MSIL">
<HintPath>packages\io.Connect.NET.1.12.0.0\lib\net45\ioConnectNET.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=2.0.17.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>packages\log4net.2.0.17\lib\net45\log4net.dll</HintPath>
Expand Down Expand Up @@ -94,6 +94,9 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="log4net.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
Expand Down
23 changes: 23 additions & 0 deletions multi-instances/MultipleInstancesDemo/log4net.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<root>
<level value="INFO"/>
<!--<appender-ref ref="OutputDebugStringAppender"/>-->
<appender-ref ref="RollingFileAppender"/>
<!--<appender-ref ref="ColoredConsoleAppender"/>-->
</root>

<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString">
<conversionPattern value="${LOCALAPPDATA}\interop.io\io.Connect Desktop\UserData\DEMO-INTEROP.IO\logs\MultipleInstancesDemo${instance_name}%date{yyyyMMdd-HHmmss}-%processid.log" />
</file>
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
</log4net>
2 changes: 1 addition & 1 deletion multi-instances/MultipleInstancesDemo/packages.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="io.Connect.NET" version="1.10.0.0" targetFramework="net45" />
<package id="io.Connect.NET" version="1.12.0.0" targetFramework="net45" />
<package id="log4net" version="2.0.17" targetFramework="net45" />
</packages>

0 comments on commit 1ec96c7

Please sign in to comment.