kwaegel's picture

[Solved] AccessViolationException in Cloo

[EDIT] This problem was fixed by updating to the latest version of nVidia's graphics driver. Works with version 263.06 and later.

I have been beating my head against some AccessViolationExceptions in Cloo for some months now, and I finally think I found the cause. The new code nythrix put in showing what threads were interacting with the Cloo objects was the tip off. Thanks nythrix!

This error appears to occur when ComputeResource objects constructed with a OpenCL/GL shared context are freed by a different thread then they were created by. In the cases I have tested, this appears to be the system cleanup thread, not the OpenTK thread calling Game.Dispose() . The latter thread is the safe thread to use.

As near as I can tell with my general lack of knowledge of OpenGL, it looks like only the main thread has a valid OpenGL context. The other threads do not and thus are not allowed to access (i.e. free) the shared blocks of memory.

I have included some code showing a test case below. This is a very stripped down version of another testing program I have written, and thus does not really do anything. To cause this error, either the second or third line in the Game.Dispose() function should be commented out. To fix this error in larger programs, I think that every ComputeResource object needs to be disposed of by the same thread that created it.

// AccessViolationException test case.
namespace ClooTest
{
    using System;
    using System.Drawing;
    using System.Threading;
    using System.Diagnostics;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
 
    using OpenTK;
    using OpenTK.Graphics;
    using OpenTK.Graphics.OpenGL;
    using OpenTK.Input;
 
    using Cloo;
 
    class SimpGame : OpenTK.GameWindow
	{
		// required for OpenCL-OpenGL interop
		[DllImport("opengl32.dll")]
		extern static IntPtr wglGetCurrentDC();
 
		OpenTK.Graphics.IGraphicsContextInternal _glContext;
 
		ComputeContext _computeContext;
		ComputeCommandQueue _commandQueue;
 
        /// <summary>Creates a window with the specified title.</summary>
        public SimpGame()
            : base(400, 400, GraphicsMode.Default, "Cloo shared context tester")
        {
            VSync = VSyncMode.On;
        }
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
			GL.ClearColor(Color.Black);
 
			openCLSharedInit();
 
            // Do not need to build a program or kernel to reproduce error.
        }
 
		// Create a sharde context between OpenGL and OpenCL. 
		private void openCLSharedInit()
		{
			// select OpenCL device and platform. Need GPU for shared context.
			ComputePlatform platform = ComputePlatform.Platforms[0];
			ComputeDevice device = platform.Devices[0];
            if (device.Type != ComputeDeviceTypes.Gpu)
            {
                platform = ComputePlatform.Platforms[1];
                device = platform.Devices[0];
            }
 
            Trace.WriteLine("Creating shared context on "+ device.ToString());
 
 
			IntPtr curDC = wglGetCurrentDC();
 
			_glContext = (OpenTK.Graphics.IGraphicsContextInternal)OpenTK.Graphics.GraphicsContext.CurrentContext;
			IntPtr raw_context_handle = _glContext.Context.Handle;
			ComputeContextProperty p1 = new ComputeContextProperty(ComputeContextPropertyName.CL_GL_CONTEXT_KHR, raw_context_handle);
			ComputeContextProperty p2 = new ComputeContextProperty(ComputeContextPropertyName.CL_WGL_HDC_KHR, curDC);
			ComputeContextProperty p3 = new ComputeContextProperty(ComputeContextPropertyName.Platform, platform.Handle);
			List<ComputeContextProperty> props = new List<ComputeContextProperty>() { p1, p2, p3 };
			ComputeContextPropertyList Properties = new ComputeContextPropertyList(props);
 
			_computeContext = new ComputeContext(device.Type, Properties, null, IntPtr.Zero);
 
			//Create the command queue from the context and device
			_commandQueue = new ComputeCommandQueue(_computeContext, device, ComputeCommandQueueFlags.None);
		}
 
 
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
            if (Keyboard[Key.Escape]) { Exit(); }
        }
 
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);
        }
 
 
		public override void Dispose()
		{
			Trace.WriteLine("Dispose called in thread(" + Thread.CurrentThread.ManagedThreadId+")");
 
			// Ensure all OpenCL objects are disposed of in the main thread.
			// WARNING: Removeing any of these dispose lines will cause an AccessViolationException on program shutdown.
 
			//_commandQueue.Dispose();
			_computeContext.Dispose();
 
			base.Dispose();
 
		}
 
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static void Main()
        {
            System.Diagnostics.Trace.WriteLine("\n********** Run at " + System.DateTime.Now.ToString() + " **********");
 
			// The 'using' idiom guarantees proper resource cleanup.
			// We request 30 UpdateFrame events per second, and unlimited
			// RenderFrame events (as fast as the computer can handle).
			using (SimpGame game = new SimpGame())
			{
				game.Run(30.0);
				//game.Run();
			}
        }
    }
}

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
kwaegel's picture

Well, we were already getting exceptions that were tearing down the CLR. I just wrapped it with a bit more information. It didn't keep the program from crashing, but it let me know what went wrong. I suppose I could have just caught the exception and wrote it to the Trace output, but I was not sure if that was safe or not.

Bit of a moot point now.