Repository: opencv
Issue: cv::ocl::attachContext can only be used once & the woes of using the singleton pattern
Labels: category: ocl,RFC

##### System information (version)

- OpenCV => 3.3
- Operating System / Platform => Mac OS X 10.12.6 BuildVersion 16G29
- Compiler => Apple LLVM version 8.1.0 (clang-802.0.42)

##### Detailed description

*FYI: Example output used in description is excerpt from the full output linked below.*

This issue I stumbled upon whilst trying to write some unit tests, for an image processing pipeline that I'd neatly packaged into a standalone class. This class is responsible for setting up an OpenCL context with OpenGL sharing, e.g.:

```c
cl_context_properties contextProps[] = {
  CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE,
  (cl_context_properties) CGLGetShareGroup(CGLGetCurrentContext()),
  0
};
```

Subsequently attaching it to OpenCV with `cv::ocl::attachContext` and then handling the actual processing. 

Setting up and tearing down instances of this class in test, was producing errors left and right, so I decided to do some spelunking in `cv::ocl` to see what was going on. Then I came upon the use of [CV_SINGLETON_LAZY_INIT](https://github.com/opencv/opencv/blob/3.3.0/modules/core/src/precomp.hpp#L310), for a lot of the `getDefault` methods used in `cv::ocl`.

As can be seen from the example attached, since `OpenCLAllocator` is in fact instantiated via this singleton pattern, it makes it impossible to actually use `cv::ocl::attachContext`, more than once in the lifetime of a program. First time around it allocates an OpenCL buffer for the `UMatData`:

```
OpenCL allocate 12288 (0x3000) bytes: 0x7fd92af2b550
```

Using the initially created OpenCL context:

```
Created context (cl_context): 0x7fd92af0cc80
```

But the second time around, it tries to reuse the buffer since it's saved in the buffer pool:

```
Reuse reserved buffer: 0x7fd92af2b550
```

The problem is then, that the buffer was orphaned when the initial OpenCL context got destroyed, and is thus no longer valid under the new:

```
Created context (cl_context): 0x7fd92af2cc10
```

This inevitably leads to OpenCL errors like this:

```
[CL_INVALID_CONTEXT] : OpenCL Error : clEnqueueWriteBuffer failed: cl_mem object belongs to a different context 0x7fd92af0cc80 than the queue's context 0x7fd92af2cc10.make: *** [default] Abort trap: 6
```

I didn't go further with this example, but I'd image that `cv::ocl::Queue` suffers the same issue, since it as well, is attached as a singleton to the global `CoreTLSData` as well.

---

Now then, is the question of how to solve this problem.

On one hand using singletons like this makes it a lot easier to write methods of the `getDefault` family. On the other hand, it's a leaky abstraction, that forces consumers of the library to use the same pattern in client code as well. Singletons are inherently the nemesis of unit testing and google can quickly provide troves of reading, on the trials and tribulations of people using them. Highest ranking write-up is probably by Brian Button on [Why Singletons are Evil](https://blogs.msdn.microsoft.com/scottdensmore/2004/05/25/why-singletons-are-evil)

I see a couple of ways this could be approached.

A. An easy way; making single{ton} use an explicit requirement for consumers setting up custom OpenCL contexts, through beefing up the documentation of the `cv::ocl` module and explicitly `cv::ocl::attachContext`.

B. A hard way; rewriting the lifetime management part of the various classes of the module, to all live and die with the OpenCL context they got created under. The various `getDefault` methods would have to somehow tie back to the default context instance, which would have to centrally keep track of things.

C. A ? way; figure out a way to share OpenCL memory objects between different contexts, like with `gl_sharing`. (I'll admit to not having looked into this enough to know if it's even possible)

---

I wouldn't mind trying to tackle B, but I would definitely need some supervision/guidance if that's the way to go.

Thoughts?

CC @alalek @ilya-lavrenov @apavlenko @vpisarev 

##### Steps to reproduce

Minimal example program available in this gist (also attached as .zip): https://gist.github.com/rhardih/8cef29ccefe7af9166a2aa03799ca3ec

[8cef29ccefe7af9166a2aa03799ca3ec-a0e1354dd4db24d01ccd2cf144fcb070c9926236.zip](https://github.com/opencv/opencv/files/1287661/8cef29ccefe7af9166a2aa03799ca3ec-a0e1354dd4db24d01ccd2cf144fcb070c9926236.zip)