txdv / LibuvSharp

.NET bindings for libuv
176 stars 41 forks source link

AccessViolationException errors #7

Closed ThatRendle closed 9 years ago

ThatRendle commented 10 years ago

I'm working on a version of my Flux web server using LibuvSharp: https://github.com/markrendle/flux/tree/libuv

My test console application works fine until I hit it with around 10,000 requests using weighttp -n 10000 -t 4 -c 8, at which point I start getting AccessViolationException thrown all over the place.

It also gets thrown sometimes when running the TcpFixture.Stress test (using R# test runner).

This is on a fairly monster PC, overclocked i7-4770, 32GB RAM, SSDs, etc.

I'm going to do some investigation of my own today, but thought I'd open an issue to get other eyes on the problem.

txdv commented 10 years ago

One of the problems currently, that is not a problem on mono(or lets say it works on mono), is that the StaticEnd callback during long running applications will get collected

https://github.com/txdv/LibuvSharp/blob/master/LibuvSharp%2FUVFile.cs#L68

int r = uv_fs_close(loop.NativeHandle, fsr.Handle, FileDescriptor, FileSystemRequest.StaticEnd);

I'm using this everywhere. Now this code literally is the same as

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void uv_fs_cb(IntPtr IntPtr); // this is already in the code

int r = uv_fs_close(loop.NativeHandle, fsr.Handle, FileDescriptor, new uv_fs_cb(FileSystemRequest.StaticEnd)); // this is the literal translation

It creates a temporary delegate which gets collected therefore resulting in a faulty pointer therefore a probable AccessViolationException. I tried to create a static delegate which contains this function and is never collected, but then the Test just stop at some point, on mono as well as on the .net framework.

Didn't investigate it further yet, because it is hard to debug.

txdv commented 10 years ago

This is one of the problems which keeps me releasing it on NuGet. A real headache.

ThatRendle commented 10 years ago

It's because you're using an implicitly created delegate for the callback.

I changed CallbackPermaRequest in my in-project copy:

public static readonly Handle.callback StaticEnd = StaticEndImpl;

private static void StaticEndImpl(IntPtr ptr, int status)
{
    var obj = PermaRequest.GetObject<CallbackPermaRequest>(ptr);
    if (obj == null) {
        throw new Exception("Target is null");
    } else {
        obj.End(ptr, status);
    }
}

Seems to have fixed the problem, for my code at least.

txdv commented 10 years ago

I did exactly the same without the readonly. Did you run the test suite? When I did that it would just stop at some point.

kekekeks commented 10 years ago

I've encountered a similar issue while writing bindings for evhttp. Try to use GCHandle.Alloc on the delegate like here https://github.com/kekekeks/evhttp-sharp/blob/master/EvHttpSharp/EventHttpListener.cs#L80

terender commented 10 years ago

I changed the CallbackPermaRequest like this

    internal class CallbackPermaRequest : PermaRequest
    {
        public Handle.callback CallbackDelegate;
        public CallbackPermaRequest(int size)
            : base(size)
        {
            CallbackDelegate = new Handle.callback(StaticEnd);
        }

        public CallbackPermaRequest(RequestType type)
            : this(UV.Sizeof(type))
        {
            CallbackDelegate = new Handle.callback(StaticEnd);
        }

        public Action<int, CallbackPermaRequest> Callback { get; set; }

        protected void End(IntPtr ptr, int status)
        {
            Callback(status, this);
            Dispose();
        }

        protected void StaticEnd(IntPtr ptr, int status)
        {
            var obj = PermaRequest.GetObject<CallbackPermaRequest>(ptr);
            if (obj == null) {
                throw new Exception("Target is null");
            } else {
                obj.End(ptr, status);
            }
        }
    }

then change the calling from

uv_write_unix(cpr.Handle, NativeHandle, buf, 1, CallbackPermaRequest.StaticEnd);

to

uv_write_unix(cpr.Handle, NativeHandle, buf, 1, cpr.CallbackDelegate);

In my case, it works fine both on Windows and on mono/Linux

txdv commented 9 years ago

Do you mind making a pull request?

txdv commented 9 years ago

@terender suggestion seems reasonable.

On mono these 'on the fly' delegates on static methods are actually saved forever. On the .NET framework they are not. My assumption was that they would stay, but I have fixed it in a commit that I have locally.