From 0b70b7f35e27a6ca4a96401ab145fb5525fd7b79 Mon Sep 17 00:00:00 2001 From: odow Date: Sat, 27 Apr 2024 10:47:37 +1200 Subject: [PATCH 1/3] Add CI for julia-nightly --- .github/workflows/ci.yml | 7 +++++-- src/C_API.jl | 22 ++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49f0ed1..5b896f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,9 +22,12 @@ jobs: - version: '1' os: macos-14 arch: aarch64 + - version: 'nightly' + os: ubuntu-latest + arch: x64 steps: - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} @@ -32,6 +35,6 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v3 with: file: lcov.info diff --git a/src/C_API.jl b/src/C_API.jl index e9da455..bea6b79 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -754,22 +754,20 @@ function solve_mcp( jacobian_linear_elements::Vector{Int} = Int[], kwargs..., ) - if c_api_Path_CheckLicense(length(z), nnz) == 0 + @assert length(z) == length(lb) == length(ub) + n = length(z) + if c_api_Path_CheckLicense(n, nnz) == 0 return MCP_LicenseError, nothing, nothing + elseif nnz > typemax(Cint) + return MCP_Error, nothing, nothing + elseif n == 0 + return MCP_Solved, nothing, nothing end - @assert length(z) == length(lb) == length(ub) out_io = silent ? IOBuffer() : stdout output_data = OutputData(out_io) - GC.@preserve output_data begin - c_api_Output_SetInterface(OutputInterface(output_data)) - - n = length(z) - if n == 0 - return MCP_Solved, nothing, nothing - end - if nnz > typemax(Cint) - return MCP_Error, nothing, nothing - end + output_interface = OutputInterface(output_data) + GC.@preserve output_data output_interface begin + c_api_Output_SetInterface(output_interface) o = c_api_Options_Create() c_api_Path_AddOptions(o) c_api_Options_Default(o) From b361893dadfbd6b0175493c5a9ea190cdf26e572 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sat, 27 Apr 2024 10:55:32 +1200 Subject: [PATCH 2/3] Update C_API.jl --- src/C_API.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/C_API.jl b/src/C_API.jl index bea6b79..3e271c0 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -765,9 +765,8 @@ function solve_mcp( end out_io = silent ? IOBuffer() : stdout output_data = OutputData(out_io) - output_interface = OutputInterface(output_data) - GC.@preserve output_data output_interface begin - c_api_Output_SetInterface(output_interface) + GC.@preserve output_data begin + c_api_Output_SetInterface(OutputInterface(output_data)) o = c_api_Options_Create() c_api_Path_AddOptions(o) c_api_Options_Default(o) From ae2c15f1475ecddd9104ba68f812f53ee89750b0 Mon Sep 17 00:00:00 2001 From: odow Date: Sat, 27 Apr 2024 11:58:46 +1200 Subject: [PATCH 3/3] Update --- src/C_API.jl | 52 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/C_API.jl b/src/C_API.jl index 3e271c0..3f8afea 100644 --- a/src/C_API.jl +++ b/src/C_API.jl @@ -62,6 +62,9 @@ end function OutputInterface(output_data) _C_PRINT = @cfunction(_c_print, Cvoid, (Ptr{Cvoid}, Cint, Ptr{Cchar})) _C_FLUSH = C_NULL # flush argument is optional + # To make pointer_from_objref legal, you must ensure that output_data AND + # the Output_Interface object outlive any ccalls. We do this in solve_mcp + # using `gc_root`. return OutputInterface(pointer_from_objref(output_data), _C_PRINT, _C_FLUSH) end @@ -148,6 +151,9 @@ mutable struct Presolve_Interface jac_typ::Ptr{Cvoid} con_typ::Ptr{Cvoid} + # To make pointer_from_objref legal, you must ensure that presolve_data AND + # the Presolve_Interface object outlive any ccalls. We do this in solve_mcp + # using `gc_root`. function Presolve_Interface(presolve_data::PresolveData) return new( pointer_from_objref(presolve_data), @@ -361,7 +367,9 @@ mutable struct MCP_Interface (Ptr{Cvoid}, Cint, Ptr{Cuchar}, Cint) ) end - + # To make pointer_from_objref legal, you must ensure that interface_data + # AND the MCP_Interface object outlive any ccalls. We do this in + # solve_mcp using `gc_root`. return new( pointer_from_objref(interface_data), _C_PROBLEM_SIZE, @@ -381,10 +389,9 @@ end mutable struct MCP n::Int ptr::Ptr{Cvoid} - id_data::Union{Nothing,InterfaceData} - presolve_data::Union{Nothing,PresolveData} + function MCP(n::Int, ptr::Ptr{Cvoid}) - m = new(n, ptr, nothing, nothing) + m = new(n, ptr) finalizer(c_api_MCP_Destroy, m) return m end @@ -763,11 +770,21 @@ function solve_mcp( elseif n == 0 return MCP_Solved, nothing, nothing end - out_io = silent ? IOBuffer() : stdout - output_data = OutputData(out_io) - GC.@preserve output_data begin - c_api_Output_SetInterface(OutputInterface(output_data)) + # gc_root is used to store various objects to prevent them from being GC'd. + gc_root = IdDict() + GC.@preserve gc_root begin + out_io = silent ? IOBuffer() : stdout + output_data = OutputData(out_io) + # We shouldn't GC output_data until we exit the GC.@preserve block. + gc_root[output_data] = true + output_interface = OutputInterface(output_data) + # We shouldn't GC output_interface until we exit the GC.@preserve block. + gc_root[output_interface] = true + c_api_Output_SetInterface(output_interface) o = c_api_Options_Create() + # Options has a finalizer. We don't want that to run until we exit the + # GC.@preserve block. + gc_root[o] = true c_api_Path_AddOptions(o) c_api_Options_Default(o) m = c_api_MCP_Create(n, nnz) @@ -777,7 +794,7 @@ function solve_mcp( if jacobian_data_contiguous c_api_MCP_Jacobian_Data_Contiguous(m, true) end - m.id_data = InterfaceData( + id_data = InterfaceData( Cint(n), Cint(nnz), F, @@ -788,16 +805,25 @@ function solve_mcp( variable_names, constraint_names, ) - m_interface = MCP_Interface(m.id_data) + # We shouldn't GC id_data until we exit the GC.@preserve block. + gc_root[id_data] = true + m_interface = MCP_Interface(id_data) + # We shouldn't GC m_interface until we exit the GC.@preserve block. + gc_root[m_interface] = true c_api_MCP_SetInterface(m, m_interface) if jacobian_structure_constant && !isempty(jacobian_linear_elements) - m.presolve_data = PresolveData() do nnz, types + presolve_data = PresolveData() do nnz, types for i in jacobian_linear_elements types[i] = PRESOLVE_LINEAR end return end - presolve_interface = Presolve_Interface(m.presolve_data) + # We shouldn't GC presolve_data until we exit the GC.@preserve block. + gc_root[presolve_data] = true + presolve_interface = Presolve_Interface(presolve_data) + # We shouldn't GC presolve_interface until we exit the GC.@preserve + # block. + gc_root[presolve_interface] = true c_api_MCP_SetPresolveInterface(m, presolve_interface) end if length(kwargs) > 0 @@ -820,8 +846,8 @@ function solve_mcp( use_basics = use_basics, ) status = c_api_Path_Solve(m, info) + X = c_api_MCP_GetX(m) end # GC.@preserve - X = c_api_MCP_GetX(m) # TODO(odow): I don't know why, but manually calling MCP_Destroy was # necessary to avoid a segfault on Julia 1.0 when using LUSOL. I guess it's # something to do with the timing of when things need to get freed on the