Repository: Pyomo/pyomo

Issue:
'WinError 32' occurs when solving mixed integer model with gurobi_direct and gurobi 9.5.0
## Summary
- An error occurs when solving **mixed integer model** with **gurobi_direct** and **Gurobi 9.5.0**.
- Pyomo work well with Gurobi 9.1.2 and I got this issue after updating Gurobi  to 9.5.0.
<!-- Explain, in a clear and concise way, the command you ran and the result you were trying to achieve.
Example: "I ran `pyomo solve` to optimize a model and ..." -->

### Steps to reproduce the issue

<!-- Please include the command run and/or provide an example script that demonstrates the problem. -->

```console
python test.py
```

```python
# test.py
import pyomo.environ as pyo

m = pyo.ConcreteModel()
m.i = pyo.Set(initialize=[1, 2, 3])
m.x = pyo.Var(m.i)
m.b = pyo.Var(domain=pyo.Binary)
m.cons = pyo.Constraint(expr=sum(m.x[i] for i in m.i) >= 1)
m.obj = pyo.Objective(expr=m.b + sum(m.x[i]**2 for i in m.i), sense=pyo.minimize)
solver = pyo.SolverFactory('gurobi_direct')
solver.solve(m, tee=True)
```

### Error Message
```console
(pyomo) D:\test>python test.py
Set parameter LogFile to value "C:\Users\Shouyuan\AppData\Local\Temp\tmprcapf5jy.log"
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (win64)
Thread count: 6 physical cores, 12 logical processors, using up to 12 threads
Optimize a model with 1 rows, 4 columns and 3 nonzeros
Model fingerprint: 0xc3872581
Model has 3 quadratic objective terms
Variable types: 3 continuous, 1 integer (1 binary)
Coefficient statistics:
  Matrix range     [1e+00, 1e+00]
  Objective range  [1e+00, 1e+00]
  QObjective range [2e+00, 2e+00]
  Bounds range     [1e+00, 1e+00]
  RHS range        [1e+00, 1e+00]
Found heuristic solution: objective 1.200000e+19
Presolve removed 0 rows and 1 columns
Presolve time: 0.00s
Presolved: 1 rows, 3 columns, 3 nonzeros
Presolved model has 3 quadratic objective terms
Variable types: 3 continuous, 0 integer (0 binary)
Root relaxation presolve time: 0.00s
Root relaxation presolved: 1 rows, 3 columns, 3 nonzeros
Root relaxation presolved model has 3 quadratic objective terms
Root barrier log...

Ordering time: 0.00s

Barrier statistics:
 Free vars  : 3
 AA' NZ     : 0.000e+00
 Factor NZ  : 1.000e+00
 Factor Ops : 1.000e+00 (less than 1 second per iteration)
 Threads    : 1

                  Objective                Residual
Iter       Primal          Dual         Primal    Dual     Compl     Time
   0   2.99998200e-12 -2.99998200e-12  2.00e+00 1.00e-06  9.98e+05     0s
   1   4.73925522e+04 -4.71411758e+04  0.00e+00 9.81e-13  9.45e+04     0s
   2   6.97332386e+03 -6.87689894e+03  0.00e+00 1.42e-14  1.39e+04     0s
   3   1.03313080e+03 -9.96016005e+02  0.00e+00 0.00e+00  2.03e+03     0s
   4   1.55827078e+02 -1.41412868e+02  0.00e+00 0.00e+00  2.97e+02     0s
   5   2.46134258e+01 -1.88847348e+01  0.00e+00 0.00e+00  4.35e+01     0s
   6   4.36798496e+00 -1.95469261e+00  0.00e+00 0.00e+00  6.32e+00     0s
   7   1.02275435e+00  1.45009525e-01  0.00e+00 0.00e+00  8.78e-01     0s
   8   4.18151444e-01  3.28531393e-01  0.00e+00 0.00e+00  8.96e-02     0s
   9   3.34808648e-01  3.33331705e-01  0.00e+00 0.00e+00  1.48e-03     0s
  10   3.33334807e-01  3.33333333e-01  0.00e+00 0.00e+00  1.47e-06     0s
  11   3.33333335e-01  3.33333333e-01  0.00e+00 0.00e+00  1.48e-09     0s

Barrier solved model in 11 iterations and 0.01 seconds (0.00 work units)
Optimal objective 3.33333335e-01


Root relaxation: objective 3.333333e-01, 0 iterations, 0.01 seconds (0.00 work units)

    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

*    0     0               0       0.3333333    0.33333  0.00%     -    0s

Explored 1 nodes (0 simplex iterations) in 0.01 seconds (0.00 work units)
Thread count was 12 (of 12 available processors)

Solution count 1: 0.333333

Optimal solution found (tolerance 1.00e-04)
Best objective 3.333333348085e-01, best bound 3.333333348085e-01, gap 0.0000%
Set parameter LogFile to value ""
Traceback (most recent call last):
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 182, in pop
    os.remove(filename)
PermissionError: [WinError 32] 另一个程序正在使用此文件，进程无法访问。: 'C:\\Users\\Shouyuan\\AppData\\Local\\Temp\\tmprcapf5jy.log'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 10, in <module>
    solver.solve(m, tee=True)
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\solvers\plugins\solvers\direct_solver.py", line 151, in solve
    result = self._postsolve()
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\solvers\plugins\solvers\gurobi_direct.py", line 742, in _postsolve
    TempfileManager.pop(remove=not self._keepfiles)
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 190, in pop
    os.remove(filename)
PermissionError: [WinError 32] 另一个程序正在使用此文件，进程无法访问。: 'C:\\Users\\Shouyuan\\AppData\\Local\\Temp\\tmprcapf5jy.log'
```
The Error Message in English is
```console
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\Users\\Shouyuan\\AppData\\Local\\Temp\\tmprcapf5jy.log'
```

### Information on your system

Pyomo version: 6.1.2
Python version: 3.8.12
Operating system: Tested on Windows 10 and Windows 11 x64
How Pyomo was installed (PyPI, conda, source): conda
Solver (if applicable): Gurobi 9.5.0 called by gurobi_direct 


### Additional information
- The error still occurs with `tee = False` 
- The error disappears if the binary variable `b` is removed
- The error disappears if the variable `x` is a scalar
- The error disappears if the constraint `cons` is removed
- There is a similar issue #285 with CPLEX direct
- The error exists when working with both Gurobi 9.5.0 from pip and conda



Discussion:
The problem still exists in Pyomo 6.2.
This appears to either be a bug in Gurobi 9.5, or a timing issue on windows.  Windows will not allow you to delete a file if *any* process has opened it.  The `gurobi_direct` solver interface tells Gurobi to close the log file after the solve by changing the `LogFile` parameter - you see this happening in your trace above:
```
Set parameter LogFile to value ""
```
...which should cause Gurobi to stop logging and allow the TempFileManager to delete the temporary file.  There is the possibility that in the situations you mention above that Gurobi is failing to close the log file.  There is also the possibility that while Gurobi has closed the file, Windows hasn't had a chance to release all the locks before the `gurobi_direct` interface attempts to delete the file.  If it is the former, then the bug will have to be resolved by Gurobi.  Pyomo added logic to the `TempFileManager` in 6.2 to try and address the latter case (it will pause and retry the file deletion if the first attempt fails).  That should raise a slightly different stack trace if it is working correctly.  Can you post the error message you are getting in Pyomo 6.2?

As a workaround, try calling `solve()` with:
```python
solver.solve(m, tee=True, keepfiles=True)
```
Pyomo will not attempt to delete the log file (so you should not see the exception), BUT, it will leave the gurobi log file in your temp directory after Python exits.
> This appears to either be a bug in Gurobi 9.5, or a timing issue on windows. Windows will not allow you to delete a file if _any_ process has opened it. The `gurobi_direct` solver interface tells Gurobi to close the log file after the solve by changing the `LogFile` parameter - you see this happening in your trace above:
> 
> ```
> Set parameter LogFile to value ""
> ```
> 
> ...which should cause Gurobi to stop logging and allow the TempFileManager to delete the temporary file. There is the possibility that in the situations you mention above that Gurobi is failing to close the log file. There is also the possibility that while Gurobi has closed the file, Windows hasn't had a chance to release all the locks before the `gurobi_direct` interface attempts to delete the file. If it is the former, then the bug will have to be resolved by Gurobi. Pyomo added logic to the `TempFileManager` in 6.2 to try and address the latter case (it will pause and retry the file deletion if the first attempt fails). That should raise a slightly different stack trace if it is working correctly. Can you post the error message you are getting in Pyomo 6.2?
> 
> As a workaround, try calling `solve()` with:
> 
> ```python
> solver.solve(m, tee=True, keepfiles=True)
> ```
> 
> Pyomo will not attempt to delete the log file (so you should not see the exception), BUT, it will leave the gurobi log file in your temp directory after Python exits.

Thank you so much.  Your solution works that the error will not be raised with `keepfiles = True`. I read the code of `gurobi_direct.py`,  and I see Pyomo retrieves values directly from the `gurobipy.Model` object rather than the log file. I'm curious about whether the log file can be disabled with direct solver interfaces?
Here is the output of Pyomo 6.2
```
(pyomo) D:\test>python test.py
Set parameter LogFile to value "C:\Users\Shouyuan\AppData\Local\Temp\tmp7a89vt8d.log"
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (win64)
Thread count: 6 physical cores, 12 logical processors, using up to 12 threads
Optimize a model with 1 rows, 4 columns and 3 nonzeros
Model fingerprint: 0xc3872581
Model has 3 quadratic objective terms
Variable types: 3 continuous, 1 integer (1 binary)
Coefficient statistics:
  Matrix range     [1e+00, 1e+00]
  Objective range  [1e+00, 1e+00]
  QObjective range [2e+00, 2e+00]
  Bounds range     [1e+00, 1e+00]
  RHS range        [1e+00, 1e+00]
Found heuristic solution: objective 1.200000e+19
Presolve removed 0 rows and 1 columns
Presolve time: 0.00s
Presolved: 1 rows, 3 columns, 3 nonzeros
Presolved model has 3 quadratic objective terms
Variable types: 3 continuous, 0 integer (0 binary)
Root relaxation presolve time: 0.01s
Root relaxation presolved: 1 rows, 3 columns, 3 nonzeros
Root relaxation presolved model has 3 quadratic objective terms
Root barrier log...

Ordering time: 0.00s

Barrier statistics:
 Free vars  : 3
 AA' NZ     : 0.000e+00
 Factor NZ  : 1.000e+00
 Factor Ops : 1.000e+00 (less than 1 second per iteration)
 Threads    : 1

                  Objective                Residual
Iter       Primal          Dual         Primal    Dual     Compl     Time
   0   2.99998200e-12 -2.99998200e-12  2.00e+00 1.00e-06  9.98e+05     0s
   1   4.73925522e+04 -4.71411758e+04  0.00e+00 9.81e-13  9.45e+04     0s
   2   6.97332386e+03 -6.87689894e+03  0.00e+00 1.42e-14  1.39e+04     0s
   3   1.03313080e+03 -9.96016005e+02  0.00e+00 0.00e+00  2.03e+03     0s
   4   1.55827078e+02 -1.41412868e+02  0.00e+00 0.00e+00  2.97e+02     0s
   5   2.46134258e+01 -1.88847348e+01  0.00e+00 0.00e+00  4.35e+01     0s
   6   4.36798496e+00 -1.95469261e+00  0.00e+00 0.00e+00  6.32e+00     0s
   7   1.02275435e+00  1.45009525e-01  0.00e+00 0.00e+00  8.78e-01     0s
   8   4.18151444e-01  3.28531393e-01  0.00e+00 0.00e+00  8.96e-02     0s
   9   3.34808648e-01  3.33331705e-01  0.00e+00 0.00e+00  1.48e-03     0s
  10   3.33334807e-01  3.33333333e-01  0.00e+00 0.00e+00  1.47e-06     0s
  11   3.33333335e-01  3.33333333e-01  0.00e+00 0.00e+00  1.48e-09     0s

Barrier solved model in 11 iterations and 0.02 seconds (0.00 work units)
Optimal objective 3.33333335e-01


Root relaxation: objective 3.333333e-01, 0 iterations, 0.01 seconds (0.00 work units)

    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

*    0     0               0       0.3333333    0.33333  0.00%     -    0s

Explored 1 nodes (0 simplex iterations) in 0.02 seconds (0.00 work units)
Thread count was 12 (of 12 available processors)

Solution count 1: 0.333333

Optimal solution found (tolerance 1.00e-04)
Best objective 3.333333348085e-01, best bound 3.333333348085e-01, gap 0.0000%
Set parameter LogFile to value ""
Traceback (most recent call last):
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 433, in _remove_filesystem_object
    os.remove(name)
PermissionError: [WinError 32] 另一个程序正在使用此文件，进程无法访问。: 'C:\\Users\\Shouyuan\\AppData\\Local\\Temp\\tmp7a89vt8d.log'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 10, in <module>
    solver.solve(m,tee=True)
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\solvers\plugins\solvers\direct_solver.py", line 151, in solve
    result = self._postsolve()
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\solvers\plugins\solvers\gurobi_direct.py", line 742, in _postsolve
    TempfileManager.pop(remove=not self._keepfiles)
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 189, in pop
    ctx.release(remove)
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 408, in release
    self._remove_filesystem_object(name)
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 441, in _remove_filesystem_object
    os.remove(name)
PermissionError: [WinError 32] 另一个程序正在使用此文件，进程无法访问。: 'C:\\Users\\Shouyuan\\AppData\\Local\\Temp\\tmp7a89vt8d.log'
Exception ignored in: <function TempfileContext.__del__ at 0x0000015CDCBAD3A0>
Traceback (most recent call last):
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 238, in __del__
    self.release()
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 408, in release
    self._remove_filesystem_object(name)
  File "D:\Programs\miniconda3\envs\pyomo\lib\site-packages\pyomo\common\tempfiles.py", line 441, in _remove_filesystem_object
    os.remove(name)
PermissionError: [WinError 32] 另一个程序正在使用此文件，进程无法访问。: 'C:\\Users\\Shouyuan\\AppData\\Local\\Temp\\tmp7a89vt8d.log'
```

Resolving patch:
diff --git a/pyomo/solvers/plugins/solvers/gurobi_direct.py b/pyomo/solvers/plugins/solvers/gurobi_direct.py
--- a/pyomo/solvers/plugins/solvers/gurobi_direct.py
+++ b/pyomo/solvers/plugins/solvers/gurobi_direct.py
@@ -160,9 +160,9 @@ def _apply_solver(self):
         else:
             self._solver_model.setParam('OutputFlag', 0)
 
-        self._solver_model.setParam('LogFile', self._log_file)
-
         if self._keepfiles:
+            # Only save log file when the user wants to keep it.
+            self._solver_model.setParam('LogFile', self._log_file)
             print("Solver log file: "+self._log_file)
 
         # Options accepted by gurobi (case insensitive):
@@ -205,8 +205,11 @@ def _apply_solver(self):
 
         self._solver_model.optimize(self._callback)
         self._needs_updated = False
-
-        self._solver_model.setParam('LogFile', 'default')
+        
+        if self._keepfiles:
+            # Change LogFile to make Gurobi close the original log file.
+            # May not work for all Gurobi versions, like ver. 9.5.0.
+            self._solver_model.setParam('LogFile', 'default')
 
         # FIXME: can we get a return code indicating if Gurobi had a significant failure?
         return Bunch(rc=None, log=None)


Test patch: