cancel
Showing results for 
Search instead for 
Did you mean: 

Experiences around the nCo 3.0 RfcDestinationManager

Former Member
0 Kudos

Hello everyone,

I'm very interested in your application architecture around the .NET connector 3.0.

For example I just don't manage to be friends with the RfcDestinationManager

Example 1:

Okay so there's this MAX_POOL_SIZE and POOL_SIZE setting. According to the developers manual it's exactly the same(?). Whatever. And some piece of documentation I found (some SAP blog) says we cannot grab and manage a connection ourselves and we have to rely on the RfcDestinationManager to return one. That's a good thing. In theory. Indeed this is where it gets complicated.

I wrote a small multi threaded console application to simulate parallel connections. All the console application does is to Ping() the target SAP system. With MAX_POOL_SIZE set to 5 (default value?) I sooner or later run into a SAP.Middleware.Connector.RfcResourceException: Unable to allocate client in pool [NAME=xxx USER=xxx CLIENT=xxx LANG=xxx ASHOST=xxx ASHOST=xxx SYSNR=xxx SYSID=xxx]. MAX_POOL_SIZE [5] exceeded

at SAP.Middleware.Connector.RfcConnectionPool.GetClient()

at SAP.Middleware.Connector.RfcDestination.GetClient(Boolean forRepository)

at SAP.Middleware.Connector.RfcDestination.Ping()

at CA4MultiThread.SapClient.Ping() in D:\workspace\xxx\SapDotNetConnector\CA4MultiThread\SapClient.cs:line 104

at CA4MultiThread.Program.SapPing() in D:\workspace\xxx\SapDotNetConnector\CA4MultiThread\Program.cs:line 98

at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

at System.Threading.ThreadHelper.ThreadStart()

Bad. On one hand I cannot manage the connections myself (because the connector takes care of the connection pool) and on the other hand there's no way to check whether it's safe to submit a request to SAP. Either the connector should implement a queue to handle those scenarios or offer a possibility to check if a connection is present or can be opened.

Of course I can set the MAX_POOL_SIZE to 100 - but what's the point of MAX_POOL_SIZE then?

Example 2:

Another thing that bugs me is the registration of destination configurations.

SAP.Middleware.Connector.RfcInvalidStateException: destination configuration already initialized

at SAP.Middleware.Connector.RfcDestinationManager.RegisterDestinationConfiguration(IDestinationConfiguration config)

at CA4MultiThread.SapClient..ctor() in D:\workspace\rsom\SapDotNetConnector\CA4MultiThread\SapClient.cs:line 81

at CA4MultiThread.SapClient.get_Instance() in D:\workspace\rsom\SapDotNetConnector\CA4MultiThread\SapClient.cs:line 57

at CA4MultiThread.Program.SapPing() in D:\workspace\xxx\SapDotNetConnector\CA4MultiThread\Program.cs:line 96

at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

at System.Threading.ThreadHelper.ThreadStart()

Typically you register your destinations only once. Okay. But in multi threaded scenarios you might very well run into situations where two threads are trying to register their destinations at just the same time and there's your exception. First of all I think the RfcDestinationManager should be aware of its state and know whether it has already a configuration present or not. And the worst thing is... you cannot even check for the RfcDestinationmanager's state yourself. The only public method which comes somewhat close to that purpose is RfcDestinationManager.GetDestination()... but that throws an exception if the destination doesn't exist - so it's not exactly a solution.

So I'm tracking RfcDestinationManager's state - thread safe - in my own application now (which is not a good thing, but I prefer this approach to having some startup code in global.asax or something like that). What's your approach here?

Accepted Solutions (0)

Answers (2)

Answers (2)

hynek_petrak
Active Participant

Hi.

ad 1)

MAX_POOL_SIZE and POOL_SIZE are not the same. As per the NCo_30_ProgrammingGuide.pdf:

PoolSize (POOL_SIZE) - Gives the maximum number of RFC connections that this destination will keep in its pool. More connections can be opened (until PeakConnectionsLimit is reached), but they are closed immediately after usage.

PeakConnectionsLimit (MAX_POOL_SIZE) -
In order to prevent an unlimited number of connections to be opened (which from a certain point on would cause the entire machine’s performance to deteriorate dramatically), you can set the PeakConnectionsLimit parameter. NCo will not open further connections for this destination when this limit is reached.

So if you set POOL_SIZE to 10 and MAX_POOL_SIZE to 100, following shall happen.

  1. you open 1 thread => the application (resp. the NCo) will open one connection
  2. open 5 threads in total => 5 connections to SAP will be opened.
  3. open 15 threads in total => 15 connections to SAP will be opened.
  4. close 2, keep 13 open in total => 13 connections to SAP will be open
  5. close another 5, keep 8 open in total => 10 connections will remain open, this is to moment when POOL_SIZE becomes effective and even you need only 8 connections, 10 (in total) remain in the pool for reuse.
  6. open 2 threads again => 10 connection will be open, 2 new threads will reuse the two pooled ones
  7. open another 50 threads => you will see (in SM04 or in netstat) 60 connections open
  8. close all threads => 10 connection will remain open, for some time, for later reuse - due to POOL_SIZE = 10.
  9. open 100 threads => you'll get the above exception, because you reach the MAX_POOL_SIZE. Note that NCo may open +1 connection for it's own purpose.

ad 2) Look for a singleton pattern, how to implement it. It's the way, how to register RfcDestinationConfiguration only once.

=> why don't you register the DestinationConfiguration in your main application thread, before you start to create "Ping" threads. What you register in the main thread is available to all other threads.

=> another way to implement Singleton can be:


RfcDestination getDestination(dest as string) {

    if (RfcDestinationManager.TryGetDestination(dest) == null) {

         IDestinationConfiguration _destConfig = new IDestinationConfigurationImplementation();

         RfcDestinationManager.RegisterDestinationConfiguration(_destConfig);

    }

    return RfcDestinationManager.GetDestination(dest);

}

=> or instead of TryGetDestination(dest) use synchronized object (lock + global bool variable or application wide semaphore) to indicate, that the destination has already been registered.

Hynek

MarkusTolksdorf
Product and Topic Expert
Product and Topic Expert

Hi,

a comment from my side:

there is a lot of correct info provided by Hynek, but still some info is missing that I want to provide here. Unless you start a context for a destination with RfcSessionManager.BeginContext(destination), NCo will behave in a stateless manner and return connections to the pool for re-use after having finished a function module invocation. Hence, typically it's not the lifetime of a thread, that a connection is blocked, but only for the execution time of the function module.

Then, I wanted to point to the configuration parameter MAX_POOL_WAIT_TIME. It will allow a thread to wait for a certain time that a connection is returned to the pool, if MAX_POOL_SIZE connections are already used.

Last but not least, I wanted to point to the RfcDestinationMonitor, which allows to check the existing pools for current usage and so on.

One thing, for which I must admit that you are right: There is currently no method to check whether a IDestinationConfiiguration has been configured already. This should be changed. Try to get a destination and do the heuristic to assume that on configuration is not good.

An important thing with destination configurations is: They should be independent from the application coding. It should not be mixed into it.

Best regards,

Markus

Former Member
0 Kudos

Good Day.

I have an existing mobile project that connects to a webService. The webService connects to SAP using the .net connector 3.0. There are multiple concurrent users that make SAP calls, currently there is a random problem that occurs whereby a user gets an incorrect user for the transaction that they are trying to process. The call i make to get a destination is:

RfcDestination destination = RfcDestinationManager.GetDestination(_sapDestinationManager.Username);

_sapDestinationManager.Username being the user which if i compare to the returned destination isn't the same.

This is intermittent and have also tried to implement customDestination to try and fix this but i haven't been able to solve this as yet. I have added a code snippet below:

        private static RfcDestination GetSapRfcDestination(string authTicket, out RfcRepository sapRfcRepository,

                                                           ref string success)

        {

                AuthenticationTicket authenticationTicket;

                if (string.IsNullOrEmpty(authTicket)) // Checks if the encrypted authTicket is null, if it is the SAP call is probably not important

                    authenticationTicket = new AuthenticationTicket();

                else

                    authenticationTicket = new AuthenticationTicket(authTicket); // Decryption of string to get the username and password

                if (_sapDestinationManager == null)

                {

                    _sapDestinationManager = new SapDestinationManager();

                    RfcDestinationManager.RegisterDestinationConfiguration(_sapDestinationManager);

                }

                if (authenticationTicket.UserName == null)

                {

                    RfcDestination destination = RfcDestinationManager.GetDestination("OneMobile");

                    sapRfcRepository = destination.Repository; //Out parameter to method

                    success = "Login Successful"; //Ref parameter

                    Log.Trace("One Mobile connection: " + destination.Repository.ToString());

                    return destination;

                }

                else

                {

                    _sapDestinationManager.Username = authenticationTicket.UserName;

                    _sapDestinationManager.Password = authenticationTicket.Password;

                    RfcDestination destination = RfcDestinationManager.GetDestination("OneMobile");

                    RfcCustomDestination customDestination = destination.CreateCustomDestination();

                    customDestination.User = _sapDestinationManager.Username;

                    customDestination.Password = _sapDestinationManager.Password;

                    if (customDestination.SystemAttributes.Destination == null)

                    {

                        RfcDestinationManager.UnregisterDestinationConfiguration(_sapDestinationManager);

                        _sapDestinationManager = null;

                        success = "Name or password is incorrect"; //Ref parameter

                        sapRfcRepository = null; //Out parameter to method

                        return null;

                    }

                    else

                    {

                        sapRfcRepository = customDestination.Repository; //Out parameter to method

                        success = "Login Successful"; //Ref parameter

                    }

                    Log.Trace("CTM: " + customDestination.Repository.ToString()); //Loggin

                    return customDestination;

                }

     }

MarkusTolksdorf
Product and Topic Expert
Product and Topic Expert
0 Kudos

Hello Lesogo,

actually, your code should be different:

* Do not regularly resgister/unregister an instance of IDestinationConfiguration. Unregistering this is only needed in very rare cases. Registering the IDestinationConfiguration should be done once in the lifetime of the process - in 99% of the use cases this is the fitting approach

* Using CustomDestination like done by you is ok, but if the logon fails, you don't have to unregister the IDestinationConfiguration.Actually, in your current code you even haven't checked so far whether logon is possible. Only a Ping will do so.

* assigning "Name or password is incorrect" to a variable named success looks strange to me ...

Best regards,

Markus

Former Member
0 Kudos

Hi Markus.

Thank you for the response. Could you give me a quick sample of how you would create destinations for multiple users connecting to SAP using .net connector 3?

That would be greatly appreciated.

Thanks

hynek_petrak
Active Participant
0 Kudos

I believe you are hijacking a thread of a different topic. Try to create your own topic.

Former Member
0 Kudos

Hi I am also in same situation. How to find the RfcDestinationManager state?.

For this how did you handled thread in your program..