Repository: confident-ai/deepeval
Issue: Synthesizer ChromaDB on Windows: [WinError 32] The process cannot access the file because it is being used by another process: '.vector_db\\...\\chroma.sqlite3'
Root cause: Resource Mishandling

**Describe the bug**
Following [guide to synthesize Goldens](https://www.deepeval.com/docs/synthesizer-generate-from-docs) on Windows throws the following error: 

`[WinError 32] The process cannot access the file because it is being used by another process: '.vector_db\\pdf_test_01\\chroma.sqlite3`

Does not occur on WSL or inside a (Linux Based) [VSCode Dev Container](https://code.visualstudio.com/docs/devcontainers/containers). The issue could be because Windows does not allow to delete the current working directory, as mentioned in [this SO answer](https://stackoverflow.com/a/78623793).

**To Reproduce**
Follow [guide to synthesize Goldens](https://www.deepeval.com/docs/synthesizer-generate-from-docs) on Windows

**StackTrace**

```python
PermissionError                           Traceback (most recent call last)
Cell In[1], line 42
     34 from deepeval.synthesizer.config import ContextConstructionConfig
     36 synthesizer = Synthesizer(
     37     model=llm_deepeval_azure_openai,
     38     cost_tracking=True,
     39 )
---> 42 synthesizer.generate_goldens_from_docs(
     43     context_construction_config=ContextConstructionConfig(
     44         max_contexts_per_document=6,
     45         critic_model=llm_deepeval_azure_openai,
     46         embedder=embedding_deepeval_azure_openai,
     47     ),
     48     max_goldens_per_context=5,
     49     include_expected_output=True,
     50     document_paths=[
     51         "../data/input/pdf_test_01.pdf"
     55     ],
     56 )
     58 synthesizer.save_as(
     59     file_type='json',
     60     directory="../data/output",
     61     file_name="testwinerror"
     62 )

File c:\Users\XXX\coderepo\.venv\Lib\site-packages\deepeval\synthesizer\synthesizer.py:132, in Synthesizer.generate_goldens_from_docs(self, document_paths, include_expected_output, max_goldens_per_context, context_construction_config, _send_data)
    130 if self.async_mode:
    131     loop = get_or_create_event_loop()
--> 132     goldens = loop.run_until_complete(
    133         self.a_generate_goldens_from_docs(
    134             document_paths=document_paths,
    135             include_expected_output=include_expected_output,
    136             max_goldens_per_context=max_goldens_per_context,
    137             context_construction_config=context_construction_config,
    138             _reset_cost=False,
    139         )
    140     )
    141 else:
    142     # Generate contexts from provided docs
    143     context_generator = ContextGenerator(
    144         document_paths=document_paths,
    145         encoding=context_construction_config.encoding,
   (...)    152         max_retries=context_construction_config.max_retries,
    153     )

File c:\Users\XXX\coderepo\.venv\Lib\site-packages\nest_asyncio.py:98, in _patch_loop.<locals>.run_until_complete(self, future)
     95 if not f.done():
     96     raise RuntimeError(
     97         'Event loop stopped before Future completed.')
---> 98 return f.result()

File ~\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\Lib\asyncio\futures.py:203, in Future.result(self)
    201 self.__log_traceback = False
    202 if self._exception is not None:
--> 203     raise self._exception.with_traceback(self._exception_tb)
    204 return self._result

File ~\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\Lib\asyncio\tasks.py:277, in Task.__step(***failed resolving arguments***)
    273 try:
    274     if exc is None:
    275         # We use the `send` method directly, because coroutines
    276         # don't have `__iter__` and `__next__` methods.
--> 277         result = coro.send(None)
    278     else:
    279         result = coro.throw(exc)

File c:\Users\XXX\coderepo\.venv\Lib\site-packages\deepeval\synthesizer\synthesizer.py:223, in Synthesizer.a_generate_goldens_from_docs(self, document_paths, include_expected_output, max_goldens_per_context, context_construction_config, _reset_cost)
    210 # Generate contexts from provided docs
    211 context_generator = ContextGenerator(
    212     document_paths=document_paths,
    213     encoding=context_construction_config.encoding,
   (...)    220     max_retries=context_construction_config.max_retries,
    221 )
    222 contexts, source_files, context_scores = (
--> 223     await context_generator.a_generate_contexts(
    224         max_contexts_per_source_file=context_construction_config.max_contexts_per_document,
    225         min_contexts_per_source_file=context_construction_config.min_contexts_per_document,
    226         max_context_size=context_construction_config.max_context_length,
    227         min_context_size=context_construction_config.min_context_length,
    228     )
    229 )
    230 if self.synthesis_cost:
    231     self.synthesis_cost += context_generator.total_cost

File c:\Users\XXX\coderepo\.venv\Lib\site-packages\deepeval\synthesizer\chunking\context_generator.py:261, in ContextGenerator.a_generate_contexts(self, max_contexts_per_source_file, min_contexts_per_source_file, max_context_size, min_context_size)
    259 finally:
    260     if os.path.exists(vector_db_path):
--> 261         shutil.rmtree(vector_db_path)

File ~\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\Lib\shutil.py:787, in rmtree(path, ignore_errors, onerror, dir_fd)
    785     # can't continue even if onerror hook returns
    786     return
--> 787 return _rmtree_unsafe(path, onerror)

File ~\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\Lib\shutil.py:629, in _rmtree_unsafe(path, onerror)
    627         onerror(os.path.islink, fullname, sys.exc_info())
    628         continue
--> 629     _rmtree_unsafe(fullname, onerror)
    630 else:
    631     try:

File ~\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\Lib\shutil.py:634, in _rmtree_unsafe(path, onerror)
    632             os.unlink(fullname)
    633         except OSError:
--> 634             onerror(os.unlink, fullname, sys.exc_info())
    635 try:
    636     os.rmdir(path)

File ~\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\Lib\shutil.py:632, in _rmtree_unsafe(path, onerror)
    630 else:
    631     try:
--> 632         os.unlink(fullname)
    633     except OSError:
    634         onerror(os.unlink, fullname, sys.exc_info())

PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: '.vector_db\\pdf_test_01\\chroma.sqlite3'
```

maybe @kritinv has more context on this, also found this: https://stackoverflow.com/questions/27215462/permissionerror-winerror-32-the-process-cannot-access-the-file-because-it-is

maybe we're deleting the file without closing it first?

any updates?


```python
# Monkey patch shutil.rmtree to handle locked files better on Windows
original_rmtree = shutil.rmtree

def safe_rmtree(path, *args, **kwargs):
    """Safe version of rmtree with retries for Windows file locks"""
    if not os.path.exists(path):
        return
        
    print(f"Safe delete: {path}")
    for attempt in range(3):
        try:
            # Force garbage collection to release file handles
            gc.collect()
            time.sleep(1)
            
            # For Windows, attempt to remove file attributes that might prevent deletion
            if sys.platform == 'win32':
                os.system(f'attrib -r -s -h "{path}\\*" /s /d')
                
            # Try the original rmtree with ignore_errors=True
            kwargs['ignore_errors'] = True
            original_rmtree(path, *args, **kwargs)
            print(f"Successfully deleted {path}")
            return
        except Exception as e:
            print(f"Delete attempt {attempt+1} failed: {e}")
            time.sleep(2)
    
    print(f"Warning: Unable to delete {path} after multiple attempts")

# Apply the monkey patch
shutil.rmtree = safe_rmtree

# Function to force close ChromaDB connections
def close_chroma_clients():
    print("Forcing release of ChromaDB connections...")
    gc.collect()
    time.sleep(1)

# Register cleanup function
atexit.register(close_chroma_clients)
```

I tried this and should work

Hey @Sam120204 have you tested it (i.e. did the code run?) We don't have windows so have to rely on contribution for this one

@penguine-ip I have tested this on my personal Windows 10 machine and it worked. I had it included at the top part for the first executed file which was mostly just the given tutorial. Without this fix the aforementioned error occurs. I did try, and succeeded, to add @Sam120204 's fix by adding it to context_generator.py in a similar fashion. It's certainly crude but fixes this issue, I have NOT tested it with Linux or Mac, as I don't have access to them. I have made a pull request implementing the fix.

Did you use OpenAI API Key to test if this is working? Because I use Ollama and I'm now getting the error - file has 0 chunks, which is less than the minimum context size of 1 Adjust the `min_context_length` to no more than 0. 

The vector_db blocked issue [The process cannot access the file because it is being used by another process: '.vector_db\test\chroma.sqlite3'. The same process is using chroma db] seems to be resolved. 

@moraish I used local Ollama, I did not use OpenAI API. I used Ollama for both embedding and generation.

> Hey [@Sam120204](https://github.com/Sam120204) have you tested it (i.e. did the code run?) We don't have windows so have to rely on contribution for this one

yes, it should work. i see the latest PR