Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Upgrade IB Gateway version #90

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
212 changes: 111 additions & 101 deletions QuantConnect.IBAutomater/IBAutomater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ private enum Region { America, Europe, Asia }
private bool _gatewaySoftRestartTimedOut;
private CancellationTokenSource _gatewaySoftRestartTokenSource;

private const string _ibGatewayExecutableOriginalName = "ibgateway";
private readonly string _ibGatewayExecutableName = $"{_ibGatewayExecutableOriginalName}1";
private bool _renamedIbGatewayExcecutable;

/// <summary>
/// Event fired when the process writes to the output stream
/// </summary>
Expand Down Expand Up @@ -188,7 +192,6 @@ public IBAutomater(string ibDirectory, string ibVersion, string userName, string

_timerLogReader = new Timer(LogReaderTimerCallback, null, Timeout.Infinite, Timeout.Infinite);


Restarted += (sender, e) =>
{
StopGatewayRestartTimeoutMonitor();
Expand All @@ -209,7 +212,9 @@ public void Dispose()
StopGatewayRestartTimeoutMonitor();

// remove Java agent setting from IB configuration file
UpdateIbGatewayConfiguration(GetIbGatewayVersionPath(), false);
UpdateIbGatewayConfiguration(GetIbGatewayVersionPath(), false, false);

RenameIbGatewayProgram(true);
}

/// <summary>
Expand All @@ -218,9 +223,27 @@ public void Dispose()
/// <param name="waitForExit">true if it should wait for the IB Gateway process to exit</param>
/// <remarks>The IB Gateway application will be launched</remarks>
public StartResult Start(bool waitForExit)
{
return Start(waitForExit, false);
}

/// <summary>
/// Starts the IB Gateway
/// </summary>
/// <param name="waitForExit">true if it should wait for the IB Gateway process to exit</param>
/// <param name="isRestart">true if soft restarting the gateway</param>
/// <remarks>The IB Gateway application will be launched</remarks>
private StartResult Start(bool waitForExit, bool isRestart)
{
lock (_locker)
{
if (!_renamedIbGatewayExcecutable)
{
RenameIbGatewayProgram();
}

var currentExecutableName = GetIbGatewayExecutablePath();

var ibAutomaterPath = "IBAutomater.sh";
if (!File.Exists(ibAutomaterPath))
{
Expand Down Expand Up @@ -264,7 +287,7 @@ public StartResult Start(bool waitForExit)
}

UpdateIbGatewayIniFile();
var javaAgent = UpdateIbGatewayConfiguration(ibGatewayVersionPath, true);
var javaAgent = UpdateIbGatewayConfiguration(ibGatewayVersionPath, true, isRestart);

_timerLogReader.Change(Timeout.Infinite, Timeout.Infinite);

Expand All @@ -281,16 +304,32 @@ public StartResult Start(bool waitForExit)
_timerLogReader.Change(TimeSpan.Zero, TimeSpan.FromSeconds(1));

string fileName;
string arguments;
var arguments = $"-J-DjtsConfigDir={ibGatewayVersionPath}";
var ibGatewayExecutablePath = GetIbGatewayExecutablePath();
if (IsWindows)
{
fileName = $"{ibGatewayVersionPath}/ibgateway.exe";
arguments = string.Empty;
fileName = ibGatewayExecutablePath;
}
else
{
fileName = ibAutomaterPath;
arguments = $"{ibGatewayVersionPath} {javaAgent}";
arguments = $"{ibGatewayExecutablePath} {javaAgent} {arguments}";
}

if (isRestart)
{
// Find autorestart file
var autoRestartFilePath = Directory.GetFiles(ibGatewayVersionPath, "autorestart", SearchOption.AllDirectories).SingleOrDefault();
if (autoRestartFilePath == null)
{
OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs("IB Gateway auto-restart could not be completed. " +
"Starting gateway from zero. This could require 2FA confirmation."));
}
else
{
var sessionFolderName = Directory.GetParent(autoRestartFilePath).Name;
arguments += $" -J-DCHANNEL=stable -J-DchannelChanged=false -J-Drestart={sessionFolderName}";
}
}

var process = new Process
Expand Down Expand Up @@ -342,8 +381,7 @@ public StartResult Start(bool waitForExit)
string message;
if (_ibAutomaterInitializeEvent.WaitOne(_initializationTimeout))
{
var processName = IsWindows ? "ibgateway" : "java";

var processName = IsWindows ? Path.GetFileNameWithoutExtension(fileName) : "java";
var p = Process.GetProcessesByName(processName).FirstOrDefault();
OutputDataReceived?.Invoke(this,
p != null
Expand Down Expand Up @@ -562,10 +600,13 @@ private void OnProcessExited(object sender, EventArgs e)

if (_isRestartInProgress)
{
_ibAutomaterInitializeEvent.Reset();

OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs("Waiting for IBGateway auto-restart"));
if (!_ibAutomaterInitializeEvent.WaitOne(_initializationTimeout))

// We need to start the IBGateway again manually, we don't let it auto-restart
// so that we can make sure the automater java agent is attached to the new process.
var startResult = Start(false, true);

if (startResult.HasError)
{
TraceIbLauncherLogFile();

Expand All @@ -578,41 +619,11 @@ private void OnProcessExited(object sender, EventArgs e)

if (_isRestartInProgress)
{
OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs("IB Automater initialized."));

// find new IBGateway process (created by auto-restart)

var process = GetIbGatewayProcess();
if (process == null)
{
OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs($"IBGateway restarted process not found"));

TraceIbLauncherLogFile();

_lastStartResult = new StartResult(ErrorCode.RestartedProcessNotFound, "IBGateway process was not found after restart");

// fire Exited event so the client can reconnect or die
Exited?.Invoke(this, new ExitedEventArgs(0));
}
else
{
OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs($"IBGateway restarted process found: Id:{process.Id} - Name:{process.ProcessName}. Arguments: {GetProcessArguments(process)}"));

// fire Restarted event so the client can reconnect only (without starting IBGateway)
Restarted?.Invoke(this, new EventArgs());

process.Exited -= OnProcessExited;

// replace process
_process = process;

// we cannot add output/error redirection event handlers here as we didn't start the process

process.Exited += OnProcessExited;
process.EnableRaisingEvents = true;
}

_isRestartInProgress = false;

OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs("IB Automater restarted and initialized."));
// fire Restarted event so the client can reconnect only (without starting IBGateway)
Restarted?.Invoke(this, new EventArgs());
}
else
{
Expand All @@ -621,6 +632,8 @@ private void OnProcessExited(object sender, EventArgs e)
}
else
{
// Let's rename the gateway program back to its original name
RenameIbGatewayProgram(true);
Exited?.Invoke(this, new ExitedEventArgs(GetProcessExitCode(_process)));
}
}
Expand Down Expand Up @@ -716,7 +729,6 @@ public StartResult Restart()
/// </summary>
public void SoftRestart()
{

if (_isDisposeCalled || _gatewaySoftRestartTokenSource != null && !_gatewaySoftRestartTokenSource.IsCancellationRequested)
{
return;
Expand Down Expand Up @@ -1038,6 +1050,55 @@ private string GetIbGatewayVersionPath()
return ibGatewayVersionPath;
}

private string GetIbGatewayExecutablePath()
{
return GetIbGatewayExecutablePath(_renamedIbGatewayExcecutable ? _ibGatewayExecutableName : _ibGatewayExecutableOriginalName);
}

private string GetIbGatewayExecutablePath(string executableName)
{
var ibGatewayPath = GetIbGatewayVersionPath();
return IsLinux
? $"{ibGatewayPath}/{executableName}"
: $"{ibGatewayPath}/{executableName}.exe";
}

private string GetIbGatewayVmOptionsPath()
{
return GetIbGatewayVmOptionsPath(_renamedIbGatewayExcecutable ? _ibGatewayExecutableName : _ibGatewayExecutableOriginalName);
}

private string GetIbGatewayVmOptionsPath(string executableName)
{
var ibGatewayPath = GetIbGatewayVersionPath();
return IsLinux
? $"{ibGatewayPath}/{executableName}.vmoptions"
: $"{ibGatewayPath}/{executableName}.vmoptions";
}

private void RenameIbGatewayProgram(bool forceOriginalName = false)
{
var currentExecutableName = _ibGatewayExecutableOriginalName;
var newExecutableName = _ibGatewayExecutableName;
if (_renamedIbGatewayExcecutable)
{
currentExecutableName = _ibGatewayExecutableName;
newExecutableName = _ibGatewayExecutableOriginalName;
_renamedIbGatewayExcecutable = false;
}
else if (!forceOriginalName)
{
_renamedIbGatewayExcecutable = true;
}
else
{
return;
}

File.Move(GetIbGatewayExecutablePath(currentExecutableName), GetIbGatewayExecutablePath(newExecutableName));
File.Move(GetIbGatewayVmOptionsPath(currentExecutableName), GetIbGatewayVmOptionsPath(newExecutableName));
}

private string GetIbGatewayIniFile()
{
return Path.Combine(IsWindows ? GetIbGatewayVersionPath() : _ibDirectory, "jts.ini");
Expand Down Expand Up @@ -1065,10 +1126,10 @@ private void UpdateIbGatewayIniFile()
}
}

private string UpdateIbGatewayConfiguration(string ibGatewayVersionPath, bool enableJavaAgent)
private string UpdateIbGatewayConfiguration(string ibGatewayVersionPath, bool enableJavaAgent, bool isRestart)
{
// update IBGateway configuration file with Java agent entry
var ibGatewayConfigFile = $"{ibGatewayVersionPath}/ibgateway.vmoptions";
var ibGatewayConfigFile = GetIbGatewayVmOptionsPath();
OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs($"Updating IBGateway configuration file: {ibGatewayConfigFile}"));

var jarPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Expand Down Expand Up @@ -1109,7 +1170,7 @@ private string UpdateIbGatewayConfiguration(string ibGatewayVersionPath, bool en

if (enableJavaAgent)
{
File.WriteAllText(javaAgentConfigFileName, $"{_userName}\n{_password}\n{_tradingMode}\n{_portNumber}\n{_exportIbGatewayLogs}");
File.WriteAllText(javaAgentConfigFileName, $"{_userName}\n{_password}\n{_tradingMode}\n{_portNumber}\n{_exportIbGatewayLogs}\n{isRestart}");
}
else
{
Expand Down Expand Up @@ -1162,57 +1223,6 @@ private void TraceIbLauncherLogFile()
}
}

private string GetProcessArguments(Process process)
{
if (IsWindows)
{
// we don't need this and supporting it requires dependencies so let's just skip it
return "Not supported";
}

try
{
return File.ReadAllText($"/proc/{process.Id}/cmdline");
}
catch (Exception exception)
{
OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs($"Error reading process cmdline: {exception.Message}"));
}
return string.Empty;
}

private Process GetIbGatewayProcess()
{
var processName = IsWindows ? "ibgateway" : "java";

var processes = Process.GetProcessesByName(processName);
if (processes == null || processes.Length == 0)
{
return null;
}
else if (processes.Length > 1)
{
OutputDataReceived?.Invoke(this, new OutputDataReceivedEventArgs($"Found multiple processes named: '{processName}'. Processes: [{string.Join(",", processes.Select(p => $"pid:{p.Id}\ncmdline:\n{GetProcessArguments(p)}"))}]"));

// in linux there's a short lived java launcher process but it doesn't have our jar as argument
var filteredProcesses = processes.Where(p => GetProcessArguments(p).Contains("IBAutomater.jar", StringComparison.InvariantCultureIgnoreCase)).ToList();
if (filteredProcesses.Count != 1)
{
filteredProcesses = processes.Where(p => GetProcessArguments(p).Contains("-Drestart=", StringComparison.InvariantCultureIgnoreCase)).ToList();
if (filteredProcesses.Count != 1)
{
return null;
}
}
return filteredProcesses.Single();
}
else
{
// happy case
return processes.Single();
}
}

private void StartGatewayRestartTimeoutMonitor()
{
// Detect rare gateway restart timeouts that could leave the gateway in a stale state
Expand Down
8 changes: 7 additions & 1 deletion QuantConnect.IBAutomater/IBAutomater.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ pkill -9 java

sleep 5

rm /tmp/.X1-lock
ps -AFH

unset DISPLAY
Xvfb :1 -screen 0 1024x768x24 2>&1 >/dev/null &
export DISPLAY=:1
$1/ibgateway

ibgatewayExecutable=$1
shift

$ibgatewayExecutable "$@"
2 changes: 1 addition & 1 deletion QuantConnect.IBAutomater/QuantConnect.IBAutomater.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<OutputType>Exe</OutputType>
<TargetFrameworks>net9.0;netstandard2.1</TargetFrameworks>
<Copyright>Copyright © 2019</Copyright>
<Version>2.0.81</Version>
<Version>2.0.82</Version>
<Description>QuantConnect IBAutomater</Description>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/QuantConnect/IBAutomater</PackageProjectUrl>
Expand Down
9 changes: 6 additions & 3 deletions java/IBAutomater/src/ibautomater/IBAutomater.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ public static void premain(String args) throws Exception {
String tradingMode = argValues[2];
int portNumber = Integer.parseInt(argValues[3]);
boolean exportIbGatewayLogs = Boolean.parseBoolean(argValues[4]);
boolean restarting = Boolean.parseBoolean(argValues[5]);

IBAutomater automater = new IBAutomater(userName, password, tradingMode, portNumber, exportIbGatewayLogs);
IBAutomater automater = new IBAutomater(userName, password, tradingMode, portNumber, exportIbGatewayLogs, restarting);
}

/**
Expand All @@ -63,9 +64,11 @@ public static void premain(String args) throws Exception {
* @param portNumber The socket port number to be used for API connections
* @param exportIbGatewayLogs If true, IBGateway logs will be exported at predefined times
* (currently at startup and when unknown windows are detected)
* @param restarting If true, the automater will assume the gateway is starting after a
* soft daily restart and won't try to log in
*/
public IBAutomater(String userName, String password, String tradingMode, int portNumber, boolean exportIbGatewayLogs) {
this.settings = new Settings(userName, password, tradingMode, portNumber, exportIbGatewayLogs);
public IBAutomater(String userName, String password, String tradingMode, int portNumber, boolean exportIbGatewayLogs, boolean restarting) {
this.settings = new Settings(userName, password, tradingMode, portNumber, exportIbGatewayLogs, restarting);

try
{
Expand Down
Loading