Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9fdbc2acd | ||
|
|
a50e0cfc26 |
No files matched your search
@@ -106,7 +106,6 @@ else()
|
||||
|
||||
add_custom_target(server_layout ALL DEPENDS ${CMAKE_BINARY_DIR}/server)
|
||||
|
||||
install(DIRECTORY "${CMAKE_BINARY_DIR}/server/plugins" DESTINATION bin)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/externals/linux/lib/${CMAKE_BUILD_TYPE}/libACE-5.7.5.so" DESTINATION bin)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/externals/linux/lib/${CMAKE_BUILD_TYPE}/libOnixS.IceImpactMulticastPriceFeedHandlerCpp.so.3.16.0.0" DESTINATION bin)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/externals/src/emumba/sparkd.lic" DESTINATION bin)
|
||||
|
||||
+12
-32
@@ -1,10 +1,10 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32922.545
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api", "src\client\spark_api\spark_api.csproj", "{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libspark", "build\src\lib\libspark.vcxproj", "{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spark", "src\client\spark\Spark.csproj", "{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}"
|
||||
EndProject
|
||||
@@ -14,15 +14,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_core", "src\client\sp
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_sdk", "src\client\spark_sdk\spark_sdk.csproj", "{6963C314-83C4-4667-B0D0-C2F7AE208C29}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25} = {FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40} = {9FA60215-26AF-3EB7-AB89-D4F4B126EF40}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_test", "src\client\spark_test\spark_test.csproj", "{ECD78CC5-138F-4809-A764-2358C2AC2E82}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_websocket", "src\client\spark_websocket\spark_websocket.csproj", "{3201359D-C071-4089-9EDE-B04D759F1508}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "spark_api_server", "src\client\spark_api_server\spark_api_server.csproj", "{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -39,14 +35,14 @@ Global
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.Release|x64.Build.0 = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{DC9A8975-8211-4FC9-A9BA-C8B9D26AD481}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.Debug|x64.Build.0 = Debug|x64
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.Release|x64.ActiveCfg = Release|x64
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.Release|x64.Build.0 = Release|x64
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
|
||||
{FDC3A021-386E-3A2C-A7EB-CBE94A95BE25}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}.Debug|x64.Build.0 = Debug|x64
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}.MinSizeRel|x64.Build.0 = MinSizeRel|x64
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}.Release|x64.ActiveCfg = Release|x64
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}.Release|x64.Build.0 = Release|x64
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64
|
||||
{9FA60215-26AF-3EB7-AB89-D4F4B126EF40}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.Debug|x64.Build.0 = Debug|x64
|
||||
{9A4D4D07-1AB6-4B2B-B111-A4F34D6C267E}.MinSizeRel|x64.ActiveCfg = Release|x64
|
||||
@@ -87,22 +83,6 @@ Global
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.Release|x64.Build.0 = Release|x64
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{ECD78CC5-138F-4809-A764-2358C2AC2E82}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{3201359D-C071-4089-9EDE-B04D759F1508}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3201359D-C071-4089-9EDE-B04D759F1508}.Debug|x64.Build.0 = Debug|x64
|
||||
{3201359D-C071-4089-9EDE-B04D759F1508}.MinSizeRel|x64.ActiveCfg = Debug|x64
|
||||
{3201359D-C071-4089-9EDE-B04D759F1508}.MinSizeRel|x64.Build.0 = Debug|x64
|
||||
{3201359D-C071-4089-9EDE-B04D759F1508}.Release|x64.ActiveCfg = Release|x64
|
||||
{3201359D-C071-4089-9EDE-B04D759F1508}.Release|x64.Build.0 = Release|x64
|
||||
{3201359D-C071-4089-9EDE-B04D759F1508}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{3201359D-C071-4089-9EDE-B04D759F1508}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Debug|x64.Build.0 = Debug|x64
|
||||
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.MinSizeRel|x64.ActiveCfg = Debug|x64
|
||||
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.MinSizeRel|x64.Build.0 = Debug|x64
|
||||
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Release|x64.ActiveCfg = Release|x64
|
||||
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.Release|x64.Build.0 = Release|x64
|
||||
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.RelWithDebInfo|x64.ActiveCfg = Release|x64
|
||||
{2B8C5D01-FF78-4BFA-B332-5E430586DAF9}.RelWithDebInfo|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,216 +1,192 @@
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Google.Protobuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reactive.Linq;
|
||||
using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using mbts;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts.api.core {
|
||||
|
||||
internal class QuickbusClientChannel : ClientChannel {
|
||||
IntPtr _client;
|
||||
|
||||
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
|
||||
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
ApiNativeMethods.api_client_destroy(_client);
|
||||
_client = IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
int _req_id = 0;
|
||||
Dictionary<int, PBCallbackWrapper> _keepalive_cache = new();
|
||||
|
||||
unsafe public override Task<Result<Response>> send<Request, Response>(int method_id, Request request) {
|
||||
var id = Interlocked.Increment(ref _req_id);
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
//ApiNativeMethods.api_client_log_error($"Call {id}");
|
||||
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
|
||||
//ApiNativeMethods.api_client_log_error($"Reply {id}");
|
||||
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
|
||||
if (res.header.Code == ApiError.Success) {
|
||||
tcs.TrySetResult(new Result<Response>(res.body));
|
||||
}
|
||||
else {
|
||||
tcs.TrySetResult(new Result<Response>(res.header.Code));
|
||||
}
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Remove(id);
|
||||
}
|
||||
};
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
|
||||
var cb_wrapper = new PBCallbackWrapper(callback);
|
||||
lock (_keepalive_cache) {
|
||||
_keepalive_cache.Add(id, cb_wrapper);
|
||||
}
|
||||
ApiNativeMethods.api_client_send(
|
||||
_client, buffer, buffer.Length, cb_wrapper.callback, IntPtr.Zero);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
|
||||
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
|
||||
IntPtr temp_subscription = IntPtr.Zero;
|
||||
void unsubscribe_temp_sub() {
|
||||
// Unsubscribe late to keep qb refcount > 0 continuously
|
||||
if (temp_subscription != IntPtr.Zero) ApiNativeMethods.api_client_unsubscribe(_client, temp_subscription);
|
||||
}
|
||||
|
||||
// 'tcs' and 'temp_callback' are defined in the same scope as 'temp_subscription'
|
||||
// (rather than in the 'while loop' scope) to prevent premature garbage collection
|
||||
TaskCompletionSource<bool> tcs = null;
|
||||
ApiNativeMethods.raw_subscription_callback temp_callback =
|
||||
(event_type, changes, change_count, unused) => {
|
||||
tcs?.TrySetResult(true);
|
||||
};
|
||||
|
||||
while (true) {
|
||||
var response = await check_stream_access(method_id, qualifier);
|
||||
if (response.success) {
|
||||
if (response.value.Authorized) {
|
||||
// success. Break loop and subscribe
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// Not authorized. return empty stream
|
||||
unsubscribe_temp_sub();
|
||||
observer.OnCompleted();
|
||||
return System.Reactive.Disposables.Disposable.Empty;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Wait for stream to fire once before trying again
|
||||
unsubscribe_temp_sub();
|
||||
tcs = new TaskCompletionSource<bool>();
|
||||
temp_subscription = ApiNativeMethods.api_client_subscribe(
|
||||
_client, method, qualifier, temp_callback, IntPtr.Zero);
|
||||
await tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
// Authorized. Stream events
|
||||
unsubscribe_temp_sub();
|
||||
// Ensure 'temp_callback' and 'tcs' aren't Garbage Collected before 'temp_subscription'
|
||||
// is disposed
|
||||
GC.KeepAlive(temp_callback);
|
||||
GC.KeepAlive(tcs);
|
||||
return SubscribeNative(method, qualifier, parser, converter, observer);
|
||||
});
|
||||
Log.QBus.diag($"Subscribing for sub: '{method}/{qualifier}'");
|
||||
return result;
|
||||
}
|
||||
private Task<Result<StreamAccessResponse>> check_stream_access(int method_id, string qualifier) {
|
||||
var request = new StreamAccessRequest() { Qualifier = qualifier ?? "" };
|
||||
return send<StreamAccessRequest, StreamAccessResponse>(method_id, request);
|
||||
}
|
||||
|
||||
private unsafe IDisposable SubscribeNative<TParse, TOut>(string method, string qualifier, MessageParser<TParse> parser, Func<TParse, TOut> converter, IObserver<RawUpdate<TOut>> observer) where TParse : IMessage<TParse> {
|
||||
bool first_update = true;
|
||||
// Memory optimization. There is only one update per subscription.
|
||||
RawUpdate<TOut> the_update = new RawUpdate<TOut>();
|
||||
ApiNativeMethods.raw_subscription_callback cb = (event_type, changes, change_count, unused) => {
|
||||
the_update.Clear();
|
||||
try {
|
||||
if (event_type == ApiNativeMethods.EventType.TABLE_UPDATED) {
|
||||
if (changes != null) {
|
||||
foreach (var change in changes) {
|
||||
switch (change.type) {
|
||||
case ApiNativeMethods.ChangeType.REMOVE:
|
||||
the_update.removed_keys.Add(change.key);
|
||||
break;
|
||||
case ApiNativeMethods.ChangeType.REPLACE:
|
||||
the_update.changed_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
|
||||
the_update.changed_keys.Add(change.key);
|
||||
break;
|
||||
case ApiNativeMethods.ChangeType.CREATE:
|
||||
the_update.added_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
|
||||
the_update.added_keys.Add(change.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
observer.OnNext(the_update);
|
||||
}
|
||||
else if (event_type == ApiNativeMethods.EventType.TABLE_CREATED) {
|
||||
if (!first_update) {
|
||||
observer.OnNext(the_update.MarkAsReset());
|
||||
}
|
||||
first_update = false;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ApiNativeMethods.api_client_log_error($"Exception in subscription callback for sub: '{method}/{qualifier}' \n--\n '{e}'");
|
||||
// Sleep to let the log message get asyncronously written to disk
|
||||
Thread.Sleep(100);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
};
|
||||
var subscription = ApiNativeMethods.api_client_subscribe(_client, method, qualifier, cb, IntPtr.Zero);
|
||||
return FinalizeableAnonymousDisposable.Create((disposing) => {
|
||||
Log.QBus.diag($"Unsubscribing for sub: '{method}/{qualifier}'");
|
||||
ApiNativeMethods.api_client_unsubscribe(_client, subscription);
|
||||
subscription = IntPtr.Zero;
|
||||
GC.KeepAlive(cb);
|
||||
cb = null;
|
||||
});
|
||||
}
|
||||
|
||||
private class FinalizeableAnonymousDisposable : IDisposable {
|
||||
private Action<bool> _action;
|
||||
public FinalizeableAnonymousDisposable(Action<bool> dispose) {
|
||||
_action = dispose;
|
||||
}
|
||||
public static FinalizeableAnonymousDisposable Create(Action<bool> dispose) { return new FinalizeableAnonymousDisposable(dispose); }
|
||||
~FinalizeableAnonymousDisposable() {
|
||||
Dispose(false);
|
||||
}
|
||||
public void Dispose() {
|
||||
GC.SuppressFinalize(this);
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
var temp = _action;
|
||||
_action = null;
|
||||
temp?.Invoke(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool is_sync_call_ok() {
|
||||
return !ApiNativeMethods.api_client_is_on_qb_thread(_client);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple wrapper that can be used to prevent the premature garbage collection of a
|
||||
/// <see cref="ApiNativeMethods.raw_pb_callback"/> delegate.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is part of a workaround for an apparent bug in csharp's GarbageCollector, where the
|
||||
/// final reference to a delegate instance mustn't be removed until any and all execution has
|
||||
/// finished (even if the reference is removed by the delegate's last statemwent, a
|
||||
/// garbage-collector race may yield the infamous error, "A callback was made on a garbage
|
||||
/// collected delegate of type...").
|
||||
/// </remarks>
|
||||
class PBCallbackWrapper {
|
||||
public readonly ApiNativeMethods.raw_pb_callback callback;
|
||||
|
||||
public PBCallbackWrapper(ApiNativeMethods.raw_pb_callback callback) =>
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Google.Protobuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reactive.Linq;
|
||||
using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using mbts;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts.api.core {
|
||||
|
||||
internal class QuickbusClientChannel : ClientChannel {
|
||||
IntPtr _client;
|
||||
|
||||
public QuickbusClientChannel(ServiceContext context, string type_name, string qualifier) : base(context) {
|
||||
_client = ApiNativeMethods.api_client_create(context.native_context, type_name, qualifier);
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero);
|
||||
}
|
||||
|
||||
public override void Dispose() {
|
||||
ApiNativeMethods.api_client_destroy(_client);
|
||||
_client = IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
int _req_id = 0;
|
||||
Dictionary<int, ApiNativeMethods.raw_pb_callback> _keepalive_cache = new Dictionary<int, ApiNativeMethods.raw_pb_callback>();
|
||||
|
||||
unsafe public override Task<Result<Response>> send<Request, Response>(int method_id, Request request) {
|
||||
var id = Interlocked.Increment(ref _req_id);
|
||||
var header = new RequestHeader() {
|
||||
Method = method_id,
|
||||
RequestId = id,
|
||||
Token = _context.token.HasValue ? new AuthToken() { Token = _context.token?.Token } : null
|
||||
};
|
||||
var buffer = header.serialize(request);
|
||||
var tcs = new TaskCompletionSource<Result<Response>>();
|
||||
//ApiNativeMethods.api_client_log_error($"Call {id}");
|
||||
ApiNativeMethods.raw_pb_callback callback = (rbuffer, rlength, _) => {
|
||||
//ApiNativeMethods.api_client_log_error($"Reply {id}");
|
||||
var res = ApiUtils.deserialize<ResponseHeader, Response>(rbuffer, (long)rlength);
|
||||
if (res.header.Code == ApiError.Success) {
|
||||
tcs.TrySetResult(new Result<Response>(res.body));
|
||||
}
|
||||
else {
|
||||
tcs.TrySetResult(new Result<Response>(res.header.Code));
|
||||
}
|
||||
lock (_keepalive_cache) { _keepalive_cache.Remove(id); }
|
||||
};
|
||||
System.Diagnostics.Trace.Assert(_client != IntPtr.Zero, "Channel may have been disposed");
|
||||
lock (_keepalive_cache) { _keepalive_cache.Add(id, callback); }
|
||||
ApiNativeMethods.api_client_send(_client, buffer, buffer.Length, callback, IntPtr.Zero);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public override IObservable<RawUpdate<TOut>> subscribe<TParse,TOut>(int method_id, string method, string qualifier, MessageParser<TParse> parser, Func<TParse,TOut> converter) {
|
||||
var result = Observable.Create<RawUpdate<TOut>>(async observer => {
|
||||
IntPtr temp_subscription = IntPtr.Zero;
|
||||
void unsubscribe_temp_sub() {
|
||||
// Unsubscribe late to keep qb refcount > 0 continuously
|
||||
if (temp_subscription != IntPtr.Zero) ApiNativeMethods.api_client_unsubscribe(_client, temp_subscription);
|
||||
}
|
||||
|
||||
// 'tcs' and 'temp_callback' are defined in the same scope as 'temp_subscription'
|
||||
// (rather than in the 'while loop' scope) to prevent premature garbage collection
|
||||
TaskCompletionSource<bool> tcs = null;
|
||||
ApiNativeMethods.raw_subscription_callback temp_callback =
|
||||
(event_type, changes, change_count, unused) => {
|
||||
tcs?.TrySetResult(true);
|
||||
};
|
||||
|
||||
while (true) {
|
||||
var response = await check_stream_access(method_id, qualifier);
|
||||
if (response.success) {
|
||||
if (response.value.Authorized) {
|
||||
// success. Break loop and subscribe
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// Not authorized. return empty stream
|
||||
unsubscribe_temp_sub();
|
||||
observer.OnCompleted();
|
||||
return System.Reactive.Disposables.Disposable.Empty;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Wait for stream to fire once before trying again
|
||||
unsubscribe_temp_sub();
|
||||
tcs = new TaskCompletionSource<bool>();
|
||||
temp_subscription = ApiNativeMethods.api_client_subscribe(
|
||||
_client, method, qualifier, temp_callback, IntPtr.Zero);
|
||||
await tcs.Task;
|
||||
}
|
||||
}
|
||||
|
||||
// Authorized. Stream events
|
||||
unsubscribe_temp_sub();
|
||||
// Ensure 'temp_callback' and 'tcs' aren't Garbage Collected before 'temp_subscription'
|
||||
// is disposed
|
||||
GC.KeepAlive(temp_callback);
|
||||
GC.KeepAlive(tcs);
|
||||
return SubscribeNative(method, qualifier, parser, converter, observer);
|
||||
});
|
||||
Log.QBus.diag($"Subscribing for sub: '{method}/{qualifier}'");
|
||||
return result;
|
||||
}
|
||||
private Task<Result<StreamAccessResponse>> check_stream_access(int method_id, string qualifier) {
|
||||
var request = new StreamAccessRequest() { Qualifier = qualifier ?? "" };
|
||||
return send<StreamAccessRequest, StreamAccessResponse>(method_id, request);
|
||||
}
|
||||
|
||||
private unsafe IDisposable SubscribeNative<TParse, TOut>(string method, string qualifier, MessageParser<TParse> parser, Func<TParse, TOut> converter, IObserver<RawUpdate<TOut>> observer) where TParse : IMessage<TParse> {
|
||||
bool first_update = true;
|
||||
// Memory optimization. There is only one update per subscription.
|
||||
RawUpdate<TOut> the_update = new RawUpdate<TOut>();
|
||||
ApiNativeMethods.raw_subscription_callback cb = (event_type, changes, change_count, unused) => {
|
||||
the_update.Clear();
|
||||
try {
|
||||
if (event_type == ApiNativeMethods.EventType.TABLE_UPDATED) {
|
||||
if (changes != null) {
|
||||
foreach (var change in changes) {
|
||||
switch (change.type) {
|
||||
case ApiNativeMethods.ChangeType.REMOVE:
|
||||
the_update.removed_keys.Add(change.key);
|
||||
break;
|
||||
case ApiNativeMethods.ChangeType.REPLACE:
|
||||
the_update.changed_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
|
||||
the_update.changed_keys.Add(change.key);
|
||||
break;
|
||||
case ApiNativeMethods.ChangeType.CREATE:
|
||||
the_update.added_items.Add(converter(parser.ParseFrom(new UnmanagedMemoryStream(change.buffer, change.buffer_len))));
|
||||
the_update.added_keys.Add(change.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
observer.OnNext(the_update);
|
||||
}
|
||||
else if (event_type == ApiNativeMethods.EventType.TABLE_CREATED) {
|
||||
if (!first_update) {
|
||||
observer.OnNext(the_update.MarkAsReset());
|
||||
}
|
||||
first_update = false;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ApiNativeMethods.api_client_log_error($"Exception in subscription callback for sub: '{method}/{qualifier}' \n--\n '{e}'");
|
||||
// Sleep to let the log message get asyncronously written to disk
|
||||
Thread.Sleep(100);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
};
|
||||
var subscription = ApiNativeMethods.api_client_subscribe(_client, method, qualifier, cb, IntPtr.Zero);
|
||||
return FinalizeableAnonymousDisposable.Create((disposing) => {
|
||||
Log.QBus.diag($"Unsubscribing for sub: '{method}/{qualifier}'");
|
||||
ApiNativeMethods.api_client_unsubscribe(_client, subscription);
|
||||
subscription = IntPtr.Zero;
|
||||
GC.KeepAlive(cb);
|
||||
cb = null;
|
||||
});
|
||||
}
|
||||
|
||||
private class FinalizeableAnonymousDisposable : IDisposable {
|
||||
private Action<bool> _action;
|
||||
public FinalizeableAnonymousDisposable(Action<bool> dispose) {
|
||||
_action = dispose;
|
||||
}
|
||||
public static FinalizeableAnonymousDisposable Create(Action<bool> dispose) { return new FinalizeableAnonymousDisposable(dispose); }
|
||||
~FinalizeableAnonymousDisposable() {
|
||||
Dispose(false);
|
||||
}
|
||||
public void Dispose() {
|
||||
GC.SuppressFinalize(this);
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
var temp = _action;
|
||||
_action = null;
|
||||
temp?.Invoke(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool is_sync_call_ok() {
|
||||
return !ApiNativeMethods.api_client_is_on_qb_thread(_client);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace spark_api_server {
|
||||
using System;
|
||||
|
||||
|
||||
class Program {
|
||||
static int Main(string[] args) {
|
||||
Console.WriteLine("Starting spark_api_server...");
|
||||
// TODO
|
||||
bts.websocket_core.WebsocketDataSync sync =
|
||||
new bts.websocket_core.WebsocketDataSync();
|
||||
bts.websocket_core.WebsocketConnectionGateway gwy =
|
||||
new bts.websocket_core.WebsocketConnectionGateway();
|
||||
gwy.init(sync);
|
||||
while (true) {
|
||||
System.Threading.Thread.Sleep(100);
|
||||
}
|
||||
//return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>$(major).$(minor).$(patch)</Version>
|
||||
<Version Condition="'$(version)'=='..'">0.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../spark_api/spark_api.csproj" />
|
||||
<ProjectReference Include="../spark_core/spark_core.csproj" />
|
||||
<ProjectReference Include="../spark_websocket/spark_websocket.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.websocket_core {
|
||||
// The implementation of this interface controls all low level
|
||||
// communication with the clients ie
|
||||
// 1.
|
||||
// It opens up a port and listens to client connections and passes on
|
||||
// the information (connection, disconnection, data) to the registered
|
||||
// IWebsocketDataSync
|
||||
// 2.
|
||||
// Sends data to the client
|
||||
public interface IWebsocketConnectionGateway {
|
||||
// This is the first call after construction. This
|
||||
// will only be called once, and the provided sync will be used
|
||||
// to send connection, disconnection and data back to the sync
|
||||
public void init(IWebsocketDataSync sync);
|
||||
// This function tells the class to start listening to client connections.
|
||||
// All the parameters (ports to open, security parameters etc) are
|
||||
// passed through the WebsocketServerParams arguments
|
||||
public void start_listening(WebsocketServerParams server_params);
|
||||
// Messages are sent to the client through this
|
||||
public void send_to_clients(
|
||||
IEnumerable<WebsocketOutgoingMessage> messages);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.websocket_core {
|
||||
// This does all the bookkeeping for conenctions and data, and passes
|
||||
// it over to the business layer
|
||||
public interface IWebsocketDataSync {
|
||||
// Called when a new client is connected
|
||||
public void handle_connection(WebsocketClientID client_id,
|
||||
WebsocketIncomingMessage? optional_message);
|
||||
// Called when a client is disconnected
|
||||
public void handle_disconnection(WebsocketClientID client_id);
|
||||
// Called when a client sends data
|
||||
public void handle_client_message(WebsocketIncomingMessage message);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.websocket_core {
|
||||
// Represents a unique client
|
||||
public class WebsocketClientID {
|
||||
readonly string ip;
|
||||
readonly string port;
|
||||
// not sure if we need this, lets go with this for now
|
||||
readonly string uuid;
|
||||
public WebsocketClientID(string ip, string port, string uuid) {
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
public override int GetHashCode() {
|
||||
return uuid.GetHashCode();
|
||||
}
|
||||
public override string ToString() {
|
||||
return uuid;
|
||||
}
|
||||
public override bool Equals(object? obj) {
|
||||
var other = obj == null ? null : obj as WebsocketClientID;
|
||||
return other != null && other.uuid == uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.websocket_core {
|
||||
|
||||
public class WebsocketConnectionGateway : IWebsocketConnectionGateway {
|
||||
private IWebsocketDataSync? _sync = null;
|
||||
private WebsocketServerParams? _server_params = null;
|
||||
|
||||
public void init(IWebsocketDataSync sync) {
|
||||
// first call
|
||||
bts.Log.assert(_sync == null);
|
||||
_sync = sync;
|
||||
}
|
||||
public void start_listening(WebsocketServerParams server_params) {
|
||||
// start listening, assert that init has been called already
|
||||
bts.Log.assert(_sync != null);
|
||||
bts.Log.assert(_server_params == null);
|
||||
_server_params = server_params;
|
||||
}
|
||||
// Messages are sent to the client through this
|
||||
public void send_to_clients(
|
||||
IEnumerable<WebsocketOutgoingMessage> messages) {
|
||||
foreach (var om in messages) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.websocket_core {
|
||||
// This does all the bookkeeping for conenctions and data, and passes
|
||||
// it over to the business layer
|
||||
public class WebsocketDataSync : IWebsocketDataSync {
|
||||
// Called when a new client is connected
|
||||
public void handle_connection(WebsocketClientID client_id,
|
||||
WebsocketIncomingMessage? optional_message) {
|
||||
var log_txt = $"WebsocketDataSync.handle_connection client_id: {client_id} " +
|
||||
$"message: {(optional_message == null ? "null" : optional_message.ToString())}";
|
||||
bts.Log.Gen.info(log_txt);
|
||||
System.Console.WriteLine(log_txt);
|
||||
}
|
||||
// Called when a client is disconnected
|
||||
public void handle_disconnection(WebsocketClientID client_id) {
|
||||
var log_txt = $"WebsocketDataSync.handle_disconnection client_id: {client_id}";
|
||||
bts.Log.Gen.info(log_txt);
|
||||
System.Console.WriteLine(log_txt);
|
||||
}
|
||||
// Called when a client sends data
|
||||
public void handle_client_message(WebsocketIncomingMessage message) {
|
||||
var log_txt = $"WebsocketDataSync.handle_client_message message: {message}";
|
||||
bts.Log.Gen.info(log_txt);
|
||||
System.Console.WriteLine(log_txt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.websocket_core {
|
||||
// Message sent from the client to the server
|
||||
public class WebsocketIncomingMessage {
|
||||
public readonly WebsocketClientID sender_client_id;
|
||||
public readonly string message_body;
|
||||
public WebsocketIncomingMessage(WebsocketClientID sender_client_id,
|
||||
string message_body) {
|
||||
this.sender_client_id = sender_client_id;
|
||||
this.message_body = message_body;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.websocket_core {
|
||||
// Message sent from the server to the client
|
||||
public class WebsocketOutgoingMessage {
|
||||
public readonly WebsocketClientID receiver_client_id;
|
||||
public readonly string message_body;
|
||||
public WebsocketOutgoingMessage(WebsocketClientID receiver_client_id,
|
||||
string message_body) {
|
||||
this.receiver_client_id = receiver_client_id;
|
||||
this.message_body = message_body;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.websocket_core {
|
||||
// Placeholder for whatever is needed for the server to start
|
||||
// listening to clients (port, security etc)
|
||||
public class WebsocketServerParams {
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>$(major).$(minor).$(patch)</Version>
|
||||
<Version Condition="'$(version)'=='..'">0.0.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../spark_core/spark_core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -522,7 +522,7 @@ void OrderServiceServer::_on_exchange_position(
|
||||
ret_pos.set_avg_filled_price(p.avg_filled_price.value());
|
||||
ret_pos.set_position(p.position);
|
||||
me->exchange_pos_publisher.add_or_update(ret_pos);
|
||||
LOG(OrderMan, Diag,
|
||||
LOG(OrderMan, Info,
|
||||
"OrderServiceServer::_on_exchange_position publishing pos id: {} "
|
||||
"key: {} res: {} account_name: {} account_id: {} "
|
||||
"account_resolved: "
|
||||
|
||||
@@ -619,6 +619,10 @@ void EMExecutionGateway::_process_pos_cb_and_fire(
|
||||
bts_account_ptr == nullptr
|
||||
? std::nullopt
|
||||
: std::optional<uint32_t>(bts_account_ptr->id());
|
||||
LOG(OrderMan, Info,
|
||||
"EMExecutionGateway::_process_pos_cb_and_fire pos: {} bts_account: "
|
||||
"{}",
|
||||
pos, account_name());
|
||||
me->exchange_position.total_exchange_positions.push_back(
|
||||
{exch_sec_desc, sec, account_name(), account_id, pos_qty,
|
||||
Price(pos.position.average_price.to_double())});
|
||||
|
||||
@@ -341,9 +341,8 @@ extern "C" void init_spark_plugin(bts::SparkServices &srv) {
|
||||
init_on_signal_plugin(srv);
|
||||
if (dyn_algo_config.enable_execution_tester())
|
||||
init_on_signal_plugin(srv);
|
||||
if (dyn_algo_config.enable_execution_tester())
|
||||
init_execution_tester_plugin(srv);
|
||||
// init_sniper_plugin(srv);
|
||||
init_execution_tester_plugin(srv);
|
||||
init_sniper_plugin(srv);
|
||||
init_task_eye_plugin(srv);
|
||||
}
|
||||
|
||||
|
||||
@@ -402,7 +402,7 @@ bool TaskingEyeAlgo::_in_shooting_zone(
|
||||
} else {
|
||||
double ee_sell_pr = theo + me->edge;
|
||||
const md::PriceSize &md_bid = sec_data.sec_book.bid().at(0);
|
||||
LOG(OrderMan, Diag,
|
||||
LOG(OrderMan, Info,
|
||||
"TaskingEyeAlgo::_in_shooting_zone sec_key: {} driver: {} theo: {} "
|
||||
"ee_price: {} md: {}/{}",
|
||||
sec_key, driver, theo, ee_sell_pr, md_bid.price(), md_bid.size());
|
||||
|
||||
Reference in new issue
Block a user