Running two REngines in parallel

Aug 3, 2011 at 10:38 AM

 

Hi R.NET guys,

thanx for a great library, R.NET is definitely the best interface to R out there!

My question is on using more than one REngine instance in parallel. I have been working on a webservice that uses R.NET to call R functions in response to calls from the web. However, instantiating more than one REngine seems to be a problem, as an example; the following code does not work:

                REngine.SetDllDirectory(@"C:\Program Files\R\R-2.13.1\bin\i386");
                REngine engine1 = REngine.CreateInstance("RDotNetA", outputDevice1);
                REngine engine2 = REngine.CreateInstance("RDotNetB", outputDevice2);          // Errors are generated and visible on the outputDevice

Perhaps I am missing something, but creating two instances should be possible right ?

Thanks for any help you might have!

 

 

Aug 3, 2011 at 1:21 PM

Hi again,

 

this might be related to discussion #5, I can see in the R.NET source that errors occur with the call to the following function:

 

        private REngine(string id, OutputMode output, CharacterDeviceAdapter adapter)
            : base(Constants.RDllName)
        {
            this.id = id;
            this.proxy = GetDefaultProxy();
            this.adapter = adapter;

            Proxy.R_setStartTime();
            Proxy.Rf_initialize_R(1, new string[] { id });
            Proxy.R_DefParams(out start);
            adapter.Install(this, ref start);
            start.R_Quiet = (output & OutputMode.Quiet) == OutputMode.Quiet;
            start.R_Slave = (output & OutputMode.Slave) == OutputMode.Slave;
            start.R_Verbose = (output & OutputMode.Verbose) == OutputMode.Verbose;
            Proxy.R_SetParams(ref start);
            Proxy.setup_Rmainloop();      // This call fails with the second call      

       }

When I check for the number of REngine instances before the setup_Rmainloop() call and skip it when the call has already been made, everything works but variable space between the two engines is shared, although they have different id's, strange !

 

greetings & thanx

Aug 3, 2011 at 5:41 PM

Wait - you're saying that all we need to do is check for engine count in the REngine constructor?

Aug 3, 2011 at 6:53 PM
Edited Aug 3, 2011 at 7:13 PM

 

Hi,

that's what I first thought, but the problem is more complicated it seems. The following simple Console application shows the problem:

using RDotNet;
using RDotNet.Devices;
using RDotNet.Internals;
using System;

namespace ConsoleApplication1
{
    class CharacterDevice : ICharacterDevice
    {
        public string ReadConsole(string prompt, int capacity, bool history)
        {
            return null;
        }

        public void WriteConsole(string output, int length, RDotNet.Internals.ConsoleOutputType outputType)
        {
        }

        public string Messages { get; set; }

        public void ShowMessage(string message)
        {
            Messages += message;
        }

        public void Busy(BusyType which)
        {
        }

        public void Callback()
        {
        }

        public YesNoCancel Ask(string question)
        {
            return YesNoCancel.Cancel;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            REngine.SetDllDirectory(@"C:\Program Files\R\R-2.13.0\bin\i386");

            CharacterDevice device1 = new CharacterDevice();
            REngine engine1 = REngine.CreateInstance("Engine1", OutputMode.Verbose, device1);
            engine1.EagerEvaluate("x <- 5");
            var numeric1 = engine1.GetSymbol("x").AsInteger();

            // the parameter numeric1 contains the value "5".
            Console.WriteLine("Parameter x equals: " + numeric1[0]);

            CharacterDevice device2 = new CharacterDevice();
            REngine engine2 = REngine.CreateInstance("Engine2", device2);
            engine2.EagerEvaluate("x <- 9");

            var numeric2 = engine1.GetSymbol("x").AsInteger(); // Note that we use engine1 to get the value.

            // the parameter numeric1 contains the value "9".
            Console.WriteLine("Parameter x equals: " + numeric2[0]);
        }
    }
}

When I use the unmodified R.NET library, the program "hangs" on the second call to REngine.CreateInstance(), as reported before in various discussions.

Then I changed the code in the R.NET library as follows:

 

		private REngine(string id, OutputMode output, CharacterDeviceAdapter adapter)
			: base(Constants.RDllName)
		{
			this.id = id;
			this.proxy = GetDefaultProxy();
			this.adapter = adapter;

			Proxy.R_setStartTime();
			Proxy.Rf_initialize_R(1, new string[] { id });
			Proxy.R_DefParams(out start);
			adapter.Install(this, ref start);
			start.R_Quiet = (output & OutputMode.Quiet) == OutputMode.Quiet;
			start.R_Slave = (output & OutputMode.Slave) == OutputMode.Slave;
			start.R_Verbose = (output & OutputMode.Verbose) == OutputMode.Verbose;
			Proxy.R_SetParams(ref start);

                        // Return when loop is already running
                        if (instances.Count > 0) 
                           return;

			Proxy.setup_Rmainloop();
		}

 

No hanging or errors this time, but as you can see in the example Console app, engine1 returns the value of parameter "x" that was set in engine2 !

So it seems that both engines share the same R environment, instead of having separate environments...

greetings & thanx

Aug 3, 2011 at 9:03 PM

 

Hi guys,

 

I believe I found problem with my issue as well as issues reported in previous discussion on the REngine.CreateInstance() function failing (the second time).

The problem is that R.NET loads "R.dll" with the kernel32 function "LoadLibrary". Because the dll is already loaded, this function returns the same handle the second time it is called.

Therefore, when you create two REngines, they are actually the same engine. I believe there are two solutions to this problem:

 

1) Copy the R.dll to a new location with a new name and use LoadLibrary on that dll (slow and ugly).

2) Start multiple processes and communicate with these processes (which have their R.dll in a separate memory space).

 

Both not very elegant, it's a shame R.dll use "global states" and doesn't allow for multiple R instances!

greetings

Aug 4, 2011 at 12:26 PM
Edited Aug 4, 2011 at 12:26 PM

I think you're right about it only supporting a single instance... I was playing around with the R dll in C++ before, and it didn't look like you could create multiple instances -- only really the Rmainloop function to run, and it depended on a whole lot of global variables in the dll.

What I haven't tried doing is to load and initialise instances of R.dll in separate AppDomain's. They're designed to run processes in separate memory spaces, isolated from one another, so it might even be possible to start separate, isolated instances of R too.

http://msdn.microsoft.com/en-us/library/ms173139%28v=vs.80%29.aspx

Aug 4, 2011 at 1:33 PM

Hmm that AppDomain idea looks like a red herring... same problem even if you start the instances from separate AppDomains. :/

Aug 4, 2011 at 2:10 PM


That's unfortunate. Perhaps a solution where we have a COM object (*.exe) that links to the R.dll is the best solution. That object will have it's own memory space with "private" copies of "R.dll".

I also tried copying R.dll to different names (R1.dll, R2.dll) and link to these dll's. New copies are loaded in separate space in that case  but that fails upon loading the dependencies of the dll. (with an error on corrupt memory). So, no lucky break yet!

Nov 6, 2011 at 8:57 AM
Edited Nov 6, 2011 at 6:08 PM

this is so frustrating, tried everything. The only positive thing is I learned a lot about .NET reflection, thread and process stack allocation, and application domains. Even with multiple R instances I can't get it working. Now looking at 2 webservice alternatives, I'll keep you updated..

Nov 7, 2011 at 7:41 AM

ok, did a lot of testing with http://rservecli.codeplex.com/ This actually does what I want. It's not a wrapper around r.dll, but it communicates with rserve. It works for me!

Jan 17, 2013 at 7:45 AM

I'm trying to integrate R.NET into an ASP.NET page. During the first page load everything works fine. But after reloading the page the code throws an exception at
GetFunction<setup_Rmainloop>("setup_Rmainloop")() in REngine.cs; After that w3wp.exe (ihe IIS Application Pool) hangs and CPU usage goes up to 100%.


Nov 14, 2014 at 3:35 PM
frankyhollywood wrote:
ok, did a lot of testing with http://rservecli.codeplex.com/ This actually does what I want. It's not a wrapper around r.dll, but it communicates with rserve. It works for me!
I know this tread is long dead, but still..
In response to frankyhollywood: I agree. I've tested a long time with R.NET myself, and trying to get it work in multiple threads in C#, but no success.

With RServe on windows, you can spawn multiple RServe processess (each with a distinct portnumber), locally or remotly.
Then, you can access these processess in parallel with 'backgroundworkers' in C# (one job, for each worker and each worker has a distinct rserve connection/port) .
You can use these binaries: https://github.com/SurajGupta/RserveCLI2 to plug into your c# application.
Aug 24, 2016 at 4:44 PM
Edited Aug 24, 2016 at 4:45 PM
So would it be a reasonable conclusion from this discussion that R.NET is really not suited for use in a production web application since each request generally runs on a separate thread? And to safely execute R scripts in a web application you need something like R Server to manage concurrent requests?
Aug 24, 2016 at 5:53 PM
Hello Danobri,

No, that is not a reasonable conclusion. It is true that R can only have one instance per process. "Per process" is the operative phrase here.
Look at Microsoft's WCF technology. I wrote a R-Host application that I can launch and communicate with via a named pipe.
Connect each instance of R-Host to a thread and you have a pool. Or you can launch, execute and close the R-Host with each web request.
It is not difficult and it works well in my production environment.
Aug 28, 2016 at 11:28 AM
Edited Aug 28, 2016 at 11:29 AM
In the past, I dropped an answer to a similar question on stack-overflow.
Maybe this will help: link