Refinitiv / Real-Time-SDK

Other
186 stars 129 forks source link

Macos EMA C# stuck on connecting #265

Closed maxstue-exx closed 7 months ago

maxstue-exx commented 8 months ago

Hello,

Me and my team are trying to connect to the Realtime-optimzed instance via c# (asp.net core 8) code and we managed to do so but only on the windows11 machine from my colleague, on my macos 14.2 macbook pro m1 I'm always getting an error (see below).

Does this package use some specific feature which doesn't exist on macos ? We couldn't find anything about compatibility issues. Enabling more trace logs via the config file also doesn't give more logs.

Did anyone had similar issues or maybe has some suggestion ? Is this library not capable of running on (most used)every system/os even though it uses c# which is capable to run on them ?

Code

https://developers.lseg.com/en/api-catalog/refinitiv-real-time-opnsrc/refinitiv-real-time-csharp-sdk/tutorials#ema-consumer-connecting-to-real-time-optimized It is the same code as in this tutorial just wrapped in a function which is getting called via an endpoint in asp.net core.

public Task GetRefinitivTestData()
    {
        OmmConsumer? consumer = null;
        try
        {
            // instantiate callback client
            AppClient appClient = new();

            _logger.Information("Connecting to RTO");

            // The consumer configuration class allows the customization of the consumer (OmmConsumer) 
            // interface.
            // OmmConsumerConfig config = new OmmConsumerConfig().Host("ADS:14002").UserName("user");
            //Create an instance of OmmConsumerConfig and assign the clientId and clientSecret
            OmmConsumerConfig config = new OmmConsumerConfig().ClientId("<clientId>").ClientSecret("<secret>");

            //Create an instance of the OMMConsumer class with the OMMConsumerConfig instance
            consumer = new OmmConsumer(config);

            _logger.Information("Subscribing to market data");

            var array = new OmmArray()
                .AddAscii("/IBM.N")
                .Complete();

            var batch = new ElementList()
                .AddArray(EmaRdm.ENAME_BATCH_ITEM_LIST, array)
                .Complete();

            //Subscribe to retrieve real-time market price data of JPY= from the ELEKTRON_DD service
            consumer.RegisterClient(new RequestMsg().ServiceName("ELEKTRON_DD").Payload(batch), appClient);
            // block this thread for a while, API calls OnRefreshMsg(), OnUpdateMsg() and OnStatusMsg()
            Thread.Sleep(20000); 

        }
        catch (OmmException excp)
        {
            _logger.Information($"Exception subscribing to market data: {excp.Message}");
        }
        finally
        {
            consumer?.Uninitialize();
        }

        return Task.CompletedTask;
    }
internal class AppClient : IOmmConsumerClient
    {

        public void OnRefreshMsg(RefreshMsg refreshMsg, IOmmConsumerEvent consumerEvent)
        {
            Console.WriteLine("----------------------------------");
            // an item handle uniquely identifies the request and is used to unsubscribe it
            // a closure is any user owned object passed on when making a request and is passed back in response (refresh/update/status) messages
            Console.WriteLine($"Refresh message, item Handle: {consumerEvent.Handle} Clousre: {consumerEvent.Closure}");

            //  display item and service name
            if (refreshMsg.HasMsgKey)
            {
                Console.WriteLine($"Item Name: {refreshMsg.Name()} Service Name: {refreshMsg.ServiceName()}");
            }

            Console.WriteLine($"Item State: {refreshMsg.State()}");
            // Level1 data payload is encoded as an OMM FieldList or NoData if no payload data is available
            if (DataType.DataTypes.FIELD_LIST == refreshMsg.Payload().DataType)
            {
                Decode(refreshMsg.Payload().FieldList());
            }
        }

        public void OnUpdateMsg(UpdateMsg updateMsg, IOmmConsumerEvent consumerEvent)
        {
            Console.WriteLine("----------------------------------");
            //  display item and service name
            if (updateMsg.HasMsgKey)
            {
                Console.WriteLine($"Item Name: {updateMsg.Name()} Service Name: {updateMsg.ServiceName()}");
            }
            // Level1 data payload is encoded as an OMM FieldList or NoData if no payload data is available
            if (DataType.DataTypes.FIELD_LIST == updateMsg.Payload().DataType)
            {
                Decode(updateMsg.Payload().FieldList());
            }
        }

        public void OnStatusMsg(StatusMsg statusMsg, IOmmConsumerEvent _) 
        {
            Console.WriteLine("Item Name: " + (statusMsg.HasName ? statusMsg.Name() : "<not set>"));
            Console.WriteLine("Service Name: " + (statusMsg.HasServiceName ? statusMsg.ServiceName() : "<not set>"));

            if (statusMsg.HasState)
                Console.WriteLine("Item State: " +statusMsg.State());

            Console.WriteLine();
        }
        // onAllMsg, onAckMsg, onGenericMsg
        // These callbacks are not necessary for our series of tutorials.  You can refer to the documentation
        // to better understand the purpose of these callbacks.
        public void OnAllMsg(Msg msg, IOmmConsumerEvent consumerEvent) { }
        public void OnAckMsg(AckMsg ackMsg, IOmmConsumerEvent consumerEvent) { }
        public void onGenericMsg(GenericMsg genericMSg, IOmmConsumerEvent consumerEvent) { }

        void Decode(FieldList fieldList)
        {
            foreach (var fieldEntry in fieldList)
            {
                Console.Write($"Fid: {fieldEntry.FieldId} Name: {fieldEntry.Name} DataType: {DataType.AsString(fieldEntry.Load!.DataType)} Value: ");
                if (Data.DataCode.BLANK == fieldEntry.Code)
                {
                    Console.WriteLine(" blank");
                }
                else
                {
                    switch (fieldEntry.LoadType)
                    {
                        case DataTypes.REAL:
                            Console.WriteLine(fieldEntry.OmmRealValue().AsDouble());
                            break;
                        case DataTypes.DATE:

                            Console.WriteLine($"{fieldEntry.OmmDateValue().Day} / {fieldEntry.OmmDateValue().Month} / {fieldEntry.OmmDateValue().Year}");
                            break;
                        case DataTypes.TIME:
                            Console.WriteLine($"{fieldEntry.OmmTimeValue().Hour}:{fieldEntry.OmmTimeValue().Minute}:{fieldEntry.OmmTimeValue().Second}:{fieldEntry.OmmTimeValue().Millisecond}");
                            break;
                        case DataTypes.INT:
                            Console.WriteLine(fieldEntry.IntValue());
                            break;
                        case DataTypes.UINT:
                            Console.WriteLine(fieldEntry.UIntValue());
                            break;
                        case DataTypes.ASCII:
                            Console.WriteLine(fieldEntry.OmmAsciiValue());
                            break;
                        case DataTypes.ENUM:
                            Console.WriteLine(fieldEntry.HasEnumDisplay ? fieldEntry.EnumDisplay() : fieldEntry.EnumValue());
                            break;
                        case DataTypes.RMTES:
                            Console.WriteLine(fieldEntry.OmmRmtesValue());
                            break;
                        case DataTypes.ERROR:
                            Console.WriteLine($"({fieldEntry.OmmErrorValue().ErrorCodeAsString()})");
                            break;
                        default:
                            Console.WriteLine();
                            break;
                    }
                }
            }
        }
    }

Error message

ERROR|: loggerMsg
    ClientName: EmaConfig
    Severity: Error    Text:    Unknown Channel entry element: ObjectName in Channel_1
loggerMsgEnd

ERROR|: loggerMsg
    ClientName: Consumer_1_1
    Severity: Error    Text:    login failed (timed out after waiting 45000 milliseconds) for :)
loggerMsgEnd

ERROR|: loggerMsg
    ClientName: ChannelCallbackClient
    Severity: Error    Text:    Received ChannelDown event on channel Channel_1
Instance Name Consumer_1_1
Reactor 16450261
Channel 65249694
Error Id SUCCESS
Internal sysError 0
Error Location 
Error text 
loggerMsgEnd</pre>

This issue was already raised here and they send me here to raise an issue.

Best Regards Max

L-Karchevska commented 8 months ago

@maxstue-exx Thank you for raising this issue! We created an internal Jira to reproduce and investigate it. A quick question: could you try running the application with a valid xml config file specified (refer to CSharp/Ema/Docs/EMACSharp_ConfigGuide.pdf for details)? The first error that you get in the log suggests that there's an issue with parsing the xml config, so supplying a valid one if you're not using any might be a workaround for the time being.

maxstue-exx commented 8 months ago

@L-Karchevska The config file is the one from the tutorial code

The first error also appears on the windows computer where the rest of the code works just fine. And if we/I remove the line <ObjectName value=""/> nothing changes in the behaviour on the running system. So we thought we could just ignore it.

<?xml version="1.0" encoding="UTF-8"?>
<EmaConfig>

<!-- ConsumerGroup provides set of detailed configurations to be used by named consumers                -->
<!-- Application specifies which configuration to use by setting OmmConsumerConfig::consumerName()      -->
<ConsumerGroup>
    <!-- DefaultConsumer parameter defines which consumer configuration is used by OmmConsumer          -->
    <!-- if application does not specify it through OmmConsumerConfig::consumerName()                   -->
    <!-- first consumer on the ConsumerList is a DefaultConsumer if this parameter is not specified     -->
    <DefaultConsumer value="Consumer_1"/>
    <ConsumerList>      
        <Consumer>
            <Name value="Consumer_1"/>  
            <Channel value="Channel_1"/>
            <Logger value="Logger_1"/>
            <Dictionary value="Dictionary_1"/>
            <XmlTraceToStdout value="0"/>
        </Consumer>

    </ConsumerList>
</ConsumerGroup>

<ChannelGroup>
    <ChannelList>
        <Channel>
            <Name value="Channel_1"/>

            <!-- ChannelType possible values are:                                                       -->
            <!-- ChannelType::RSSL_SOCKET    - TCP IP connection type                                   -->
            <!-- ChannelType::RSSL_ENCRYPTED - Encrypted connection type                                -->
            <!-- For RTO the ChannelType must be ChannelType::RSSL_ENCRYPTED                            -->
            <ChannelType value="ChannelType::RSSL_ENCRYPTED"/>

            <!-- EMA discovers a host and a port from RDP service discovery for the specified location 
                when both of them are not set and the session management is enable. -->
            <Location value="ap-northeast-1"/>
            <EnableSessionManagement value="1"/>
            <EncryptedProtocolType value="EncryptedProtocolType::RSSL_SOCKET"/>
            <!-- ObjectName is optional: defaulted to ""                                                -->
            <ObjectName value=""/>
        </Channel>
    </ChannelList>
</ChannelGroup>

<LoggerGroup>
    <LoggerList>
        <Logger>
            <Name value="Logger_1"/>

            <!-- LoggerType is optional:  defaulted to "File"                                           -->
            <!-- possible values: Stdout, File                                                          -->
            <LoggerType value="LoggerType::Stdout"/>

            <!-- LoggerSeverity is optional: defaulted to "Success"                                     -->
            <!-- possible values: Verbose, Success, Warning, Error, NoLogMsg                            -->
            <LoggerSeverity value="LoggerSeverity::Success"/>
        </Logger>

    </LoggerList>
</LoggerGroup>

<DictionaryGroup>
    <DictionaryList>
        <Dictionary>
            <Name value="Dictionary_1"/>

            <!-- dictionaryType is optional: defaulted to ChannelDictionary" -->
            <!-- possible values: ChannelDictionary, FileDictionary -->
            <!-- if dictionaryType is set to ChannelDictionary, file names are ignored -->
            <DictionaryType value="DictionaryType::ChannelDictionary"/>
        </Dictionary>   
    </DictionaryList>
</DictionaryGroup>
</EmaConfig>
L-Karchevska commented 8 months ago

@maxstue-exx Thank you for the details!

L-Karchevska commented 8 months ago

@maxstue-exx Could you also specify the version of RTSDK you are using?

maxstue-exx commented 8 months ago

@L-Karchevska Sure, we are on the latest(no prerelease) nuget version

    <PackageReference Include="LSEG.Eta.Core" Version="3.1.0"/>
    <PackageReference Include="LSEG.Eta.ValueAdd" Version="3.1.0"/>
    <PackageReference Include="LSEG.Eta.Ansi" Version="3.1.0"/>
    <PackageReference Include="LSEG.Eta.AnsiPage" Version="3.1.0"/>
    <PackageReference Include="LSEG.Ema.Core" Version="3.1.0"/>
    <PackageReference Include="K4os.Compression.LZ4" Version="1.3.6"/>
maxstue-exx commented 8 months ago

@L-Karchevska Hi I just wanted to ask if this is something which is going to be fixed/changed in the neat future?

L-Karchevska commented 8 months ago

@maxstue-exx We are working on this issue, but we can't tell the exact timeframe. Could you turn on LoggerSeverity to Verbose in your configuration file, run your application and post the whole log with all error messages?

maxstue-exx commented 8 months ago

@L-Karchevska

ERROR|: loggerMsg
    ClientName: EmaConfig
    Severity: Error    Text:    Unknown Channel entry element: ObjectName in Channel_1
loggerMsgEnd

TRACE|: loggerMsg
    ClientName: Consumer_1_2
    Severity: Trace    Text:    Print out active configuration detail.
    ConfiguredName: Consumer_1
    InstanceName: Consumer_1_2
    ItemCountHint: 100000
    ServiceCountHint: 513
    MaxDispatchCountApiThread: 100
    MaxDispatchCountUserThread: 100
    RequestTimeout: 15000
    XmlTraceToStdout: False
    ObeyOpenWindow: True
    PostAckTimeout: 15000
    MaxOutstandingPosts: 100000
    DispatchMode: API_DISPATCH
    ReconnectAttemptLimit: -1
    ReconnectMinDelay: 5000
    ReconnectMaxDelay: 5000
    MsgKeyInUpdates: True
    DirectoryRequestTimeOut: 45000
    DictionaryRequestTimeOut: 45000
    RestRequestTimeOut: 15000
    LoginRequestTimeOut: 45000
loggerMsgEnd

TRACE|: loggerMsg
    ClientName: Consumer_1_2
    Severity: Trace    Text:    Successfully created Reactor.
loggerMsgEnd

TRACE|: loggerMsg
    ClientName: LoginCallbackClient
    Severity: Trace    Text:    RDMLogin request message was populated with this info: LoginRequest: 
    streamId: 1
    userName: mast1
    streaming: true
    nameType: NAME
    applicationId: 256
    applicationName: ema
    position: 1.1.1.1/net

loggerMsgEnd

TRACE|: loggerMsg
    ClientName: DirectoryCallbackClient
    Severity: Trace    Text:    RDMDirectoryRequest message was populated with Filter(s)
    RDM_DIRECTORY_SERVICE_INFO_FILTER
    RDM_DIRECTORY_SERVICE_STATE_FILTER
    RDM_DIRECTORY_SERVICE_GROUP_FILTER
    RDM_DIRECTORY_SERVICE_LOAD_FILTER
    RDM_DIRECTORY_SERVICE_DATA_FILTER
    RDM_DIRECTORY_SERVICE_LINK_FILTER
    requesting all services
loggerMsgEnd

TRACE|: loggerMsg
    ClientName: ChannelCallbackClient
    Severity: Trace    Text:    Created ChannelCallbackClient
loggerMsgEnd

TRACE|: loggerMsg
    ClientName: ChannelCallbackClient
    Severity: Trace    Text:    Attempt to connect using
    1] ENCRYPTED
    Channel name Channel_1
    Instance name Consumer_1_2
    Reactor @53473871
    InterfaceName 
    HostName 
    Port 
    CompressionType None
    TcpNodelay False
    EnableSessionMgnt True
    Location ap-northeast-1
    EncryptedProtocolType SOCKET
    EncryptedProtocolFlags ENC_NONE
    AuthenticationTimeout 10 sec
    ReconnectAttemptLimit -1
    ReconnectMinDelay 5000 msec
    ReconnectMaxDelay 5000 msec
    GuaranteedOutputBuffers 50
    NumInputBuffers 100
    SysRecvBufSize 0
    SysSendBufSize 0
    ConnectionPingTimeout 60 sec
    InitializationTimeout 60 sec

loggerMsgEnd

TRACE|: loggerMsg
    ClientName: ChannelCallbackClient
    Severity: Trace    Text:    Received ChannelOpened event on channel Channel_1
    Instance Name Consumer_1_2
loggerMsgEnd

TRACE|: loggerMsg
    ClientName: ChannelCallbackClient
    Severity: Trace    Text:    Successfully created a Reactor and Channel(s)
    Channel name(s) Channel_1
    Instance name Consumer_1_2

loggerMsgEnd

ERROR|: loggerMsg
    ClientName: Consumer_1_2
    Severity: Error    Text:    login failed (timed out after waiting 45000 milliseconds) for :)
loggerMsgEnd

ERROR|: loggerMsg
    ClientName: ChannelCallbackClient
    Severity: Error    Text:    Received ChannelDown event on channel Channel_1
    Instance Name Consumer_1_2
    Reactor 53473871
    Channel 15318576
    Error Id SUCCESS
    Internal sysError 0
    Error Location 
    Error text 
loggerMsgEnd
soranat commented 8 months ago

@maxstue-exx I am able to reproduce the same issue with macOS and this is a known issue with the dotnet runtime at https://github.com/dotnet/runtime/issues/920 which will be fixed in further release.

Please note that macOS is not used in test for RTSDK C# according to https://github.com/Refinitiv/Real-Time-SDK/blob/master/CSharp/README.md

maxstue-exx commented 8 months ago

@soranat Thanks for the info. Is there a way to workaround this issue? Can you for example set another "connectiontype" so the code won't use sockets? maybe another abstractionlayer or a completely different type ?

maxstue-exx commented 8 months ago

@soranat I have another question. If the error from above is occurring no exception is thrown or any callback via an errorclient is called. Therefore the catch and finally block will never run and never free up ressources.

Is this intended? because in the errorclient readme is says that all errors will throw a respective exception ?

soranat commented 7 months ago

@maxstue-exx The known issue from the .NET runtime breaks internal notification within the Reactor component causing EMA to wait for the channel up event from the Reactor.

There is no workaround for this issue and MacOs is not tested and supported by RTSDK C#.

maxstue-exx commented 7 months ago

@soranat In the linked ticket someone mentioned some infos and a possible solution/workaround? Is this something you can integrate?