[Cbc] Segfault / memory corruption after "assignSolver" in "CbcModel"

John Forrest john.forrest at fastercoin.com
Fri May 9 09:58:59 EDT 2014


Dirk,

Not exactly sure where the problem was - I rearranged code a bit and it 
seems OK and without a leak.  See if it fixes.

John Forrest
On 09/05/14 11:00, Dirk Eßer wrote:
> Hi John,
>
> thanks for the reply. I now have a minimal example (for some 
> definition of minimal, which you can find attached), with which I can 
> reproduce the access violation. Right now, it seems, that I have the 
> choice between
>
>   * getting a segfault when trying to clean-up all solvers
>     instanciated during the program run, or
>   * leaking a solver instance.
>
> I am not quite comfortable with option 2, since I don't know, how much 
> memory is actually leaked in this case, in particular as the actual 
> application will solve quite a few MIPs sequentially in the same 
> process. If it were only a couple of bytes per solver instance, then I 
> am almost willing accept that as "price to pay" for the preprocessing 
> step. But I don't know the inner workings of the solvers involved 
> enough to make a judgement here.
>
> Also, I am still not convinced, that the error isn't entirely in our 
> own code; right now, though, I don't see anything inherently wrong. 
> Any help appreciated.
>
>
> Cheers,
> Dirk Eßer
>
>
>
> Am 07.05.2014 19:36, schrieb John Forrest:
>> Dirk,
>>
>> The -cpp was a nice idea - but impossible to keep current - so 
>> something may be broken.
>>
>> Try using code from the Cbc/examples driver3.cpp or driver4.cpp which 
>> should give you the same flexibility.
>>
>> John Forrest
>> On 07/05/14 11:18, Dirk Eßer wrote:
>>> Hello,
>>>
>>> I have a little problem adding a preprocessing step to program doing 
>>> MIP solving using the CBC library. The problem is, that the program 
>>> fails with more or less random memory corruption (and in the better 
>>> cases: with a segfault) some of the time.
>>>
>>> The CbcModel instance is created programmatically
>>>
>>>     OsiClpSolverInterface iface;
>>>     CoinPackedMatrix pmx(false, num_columns, num_rows, num_elements,
>>>     matrix, columns, starts, lengths);
>>>
>>>     iface.setObjSense(-1.0);
>>>     iface.loadProblem(pmx, 0, 0, objective, senses, rights, 0);
>>>     iface.passInMessageHandler(&printer);
>>>
>>>     model_ptr LP(new CbcModel(iface));
>>>     ... adding stuff via LP->addObjects(...) to declare integer
>>>     constraints on variables and a few SOSes ...
>>>
>>> Things work fine, if we directly compute a solution without further 
>>> preprocessing via
>>>
>>>     LP->initialSolve();
>>>     LP->branchAndBound();
>>>
>>> We now try to add a pre-processing step to the solution process. As 
>>> a starting point, we used the driver code generated by the CBC 
>>> command line tool ("-cpp 0", which doesn't compile).
>>>
>>>     OsiSolverInterface* original_solver = LP->solver();
>>>     OsiSolverInterface* cloned_solver = original_solver->clone();
>>>     OsiSolverInterface* configured_solver = 0;
>>>
>>>     cloned_solver->passInMessageHandler(LP->messageHandler());
>>>     cloned_solver->setHintParam(OsiDoInBranchAndCut, true, OsiHintDo);
>>>
>>>     CglPreProcess processor;
>>>
>>>                 {
>>>                     CglProbing prober;
>>>                     prober.setUsingObjective(1);
>>>                     prober.setMaxPass(3);
>>>     prober.setMaxProbeRoot(cloned_solver->getNumCols());
>>>                     prober.setMaxElements(100);
>>>                     prober.setMaxLookRoot(50);
>>>                     prober.setRowCuts(3);
>>>                     processor.addCutGenerator(&prober);
>>>                 }
>>>
>>>     configured_solver =
>>>     processor.preProcessNonDefault(*cloned_solver, 2, 10);
>>>
>>>     cloned_solver->setHintParam(OsiDoInBranchAndCut, false, OsiHintDo);
>>>
>>>     if (!configured_solver)
>>>     {
>>>         delete cloned_solver;
>>>         return status::infeasible;
>>>     }
>>>
>>>     configured_solver->setHintParam(OsiDoInBranchAndCut, false,
>>>     OsiHintDo);
>>>
>>>     OsiSolverInterface* configured_clone = configured_solver->clone();
>>>
>>>     LP->assignSolver(configured_clone, true /* Kill previous solver
>>>     */); /*-- XXX --*/
>>>     LP->initialSolve();
>>>     LP->branchAndBound();
>>>
>>>     int n_cols = cloned_solver->getNumCols();
>>>     processor.postProcess(*LP->solver());
>>>
>>>     LP->assignSolver(cloned_solver, true /* Kill previous solver */);
>>>     memcpy(LP->bestSolution(), LP->solver()->getColSolution(),
>>>     n_cols * sizeof(double));
>>>
>>> It seems, that we are violating one or two unwritten laws of memory 
>>> management with CBC, but I am at loss as what we are doing wrong 
>>> here. Any help, hints, pointer to documentation, etc. is highly 
>>> appreciated.
>>>
>>> Cheers,
>>> Dirk Eßer
>>>
>>>
>>> _______________________________________________
>>> Cbc mailing list
>>> Cbc at list.coin-or.org
>>> http://list.coin-or.org/mailman/listinfo/cbc
>>
>>
>>
>> _______________________________________________
>> Cbc mailing list
>> Cbc at list.coin-or.org
>> http://list.coin-or.org/mailman/listinfo/cbc
>
>
>
> _______________________________________________
> Cbc mailing list
> Cbc at list.coin-or.org
> http://list.coin-or.org/mailman/listinfo/cbc

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://list.coin-or.org/pipermail/cbc/attachments/20140509/501b2ffe/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: test.log
Type: text/x-log
Size: 6011 bytes
Desc: not available
URL: <http://list.coin-or.org/pipermail/cbc/attachments/20140509/501b2ffe/attachment-0001.bin>
-------------- next part --------------
#if defined(_MSC_VER)
// Turn off compiler warning about long names
#  pragma warning(disable:4786)
#endif

#include <cassert>
#include <iomanip>
#include <iostream>
#include <algorithm>

#include "OsiClpSolverInterface.hpp"
#include "CbcModel.hpp"
#include "CbcCutGenerator.hpp"
#include "CbcStrategy.hpp"
#include "CglPreProcess.hpp"
#include "CoinTime.hpp"
#include "CglProbing.hpp"
#include "CglProbing.hpp"
#include "CglGomory.hpp"
#include "CglKnapsackCover.hpp"
#include "CglClique.hpp"
#include "CglMixedIntegerRounding2.hpp"
#include "CglFlowCover.hpp"
#include "CglTwomir.hpp"
#include "CbcHeuristicFPump.hpp"
#include "CbcHeuristic.hpp"
#include "CbcHeuristicGreedy.hpp"
#include "CbcHeuristicGreedy.hpp"
#include "CbcHeuristicDiveCoefficient.hpp"
#include "CbcHeuristicRINS.hpp"
#include "CbcHeuristicLocal.hpp"
#include "CbcCompareActual.hpp"


template<class T>
class Ptr
{
public:
    Ptr(): m_ptr(0) {}
    template<class U> Ptr(U* p): m_ptr(p) {}
    ~Ptr() 
        { 
            delete m_ptr; 
            // if (m_ptr) std::cout << "\nMemory leak" << std::endl;
        }
    
    T* operator-> () const { return m_ptr; }
    T& operator* () const { return *m_ptr; }
    bool operator == (const Ptr& p) const { return m_ptr == p.m_ptr; }
    bool operator != (const Ptr& p) const { return m_ptr != p.m_ptr; }
    T* get() const { return m_ptr; }
    void clear() { delete m_ptr; m_ptr = 0; }
    T* detach() 
        {
            T* p = m_ptr; m_ptr = 0; 
            return p; 
        }
    void swap(Ptr& o) throw() { std::swap(m_ptr, o.m_ptr); }

    operator bool () const { return m_ptr != 0; }

    template<class U> Ptr& operator= (U* p)
        {
            if (p != m_ptr) 
            {
                Ptr<T> v(p);
                swap(v);
            }
            return *this;
        }

private:
    Ptr(const Ptr&);
    Ptr& operator= (const Ptr&);

private:
    T* m_ptr;
};

static void
assign_solver(CbcModel& model, Ptr<OsiSolverInterface>& ptr)
{
    OsiSolverInterface* hack = ptr.detach();
    model.assignSolver(hack);
}

static void
configure_cut_generators(CbcModel& model)
{
    int n_generators = 0;
    
    CglProbing probing;
    probing.setMaxPass(1);
    probing.setMaxProbe(123);
    probing.setMaxLook(10);
    probing.setMaxElements(200);
    probing.setMaxPassRoot(1);
    probing.setMaxProbeRoot(123);
    probing.setMaxLookRoot(20);
    probing.setMaxElementsRoot(300);
    probing.setRowCuts(3);
    probing.setUsingObjective(1);
    model.addCutGenerator(&probing, -98, "Probing", true, false, false, -100, -1, -1);
    ++n_generators;
    // cbcModel->cutGenerator(n_generators - 1)->setTiming(true);

    CglGomory gomory;
    gomory.setLimitAtRoot(1000);
    gomory.setAwayAtRoot(0.005);
    model.addCutGenerator(&gomory,-98,"Gomory",true,false,false,-100,-1,-1);
    ++n_generators;
    // cbcModel->cutGenerator(n_generators - 1)->setTiming(true);

    CglKnapsackCover knapsackCover;
    model.addCutGenerator(&knapsackCover,-98,"KnapsackCover",true,false,false,-100,-1,-1);
    ++n_generators;
    // cbcModel->cutGenerator(n_generators - 1)->setTiming(true);

    CglClique clique;
    clique.setStarCliqueReport(false);
    clique.setRowCliqueReport(false);
    clique.setMinViolation(0.1);
    model.addCutGenerator(&clique,-98,"Clique",true,false,false,-100,-1,-1);
    ++n_generators;
    // cbcModel->cutGenerator(n_generators - 1)->setTiming(true);

    CglFlowCover flowCover;
    model.addCutGenerator(&flowCover,-98,"FlowCover",true,false,false,-100,-1,-1);
    ++n_generators;
    // cbcModel->cutGenerator(n_generators - 1)->setTiming(true);
        
    CglTwomir twomir;
    twomir.setMaxElements(250);
    model.addCutGenerator(&twomir,-98,"Twomir",true,false,false,-100,-1,-1);
    ++n_generators;
    // cbcModel->cutGenerator(n_generators - 1)->setTiming(true);
}

static void
configure_heuristics(CbcModel& model)
{
#if 0
    CbcHeuristicFPump heuristicFPump(model);
    heuristicFPump.setWhen(13);
    heuristicFPump.setFeasibilityPumpOptions(40);
    heuristicFPump.setFractionSmall(0.5);
    heuristicFPump.setHeuristicName("feasibility pump");
    heuristicFPump.setMaximumPasses(30);
    heuristicFPump.setMaximumRetries(6);
    heuristicFPump.setAccumulate(1);
    heuristicFPump.setDefaultRounding(0.5);
    model.addHeuristic(&heuristicFPump);

    CbcRounding rounding(model);
    rounding.setHeuristicName("rounding");
    model.addHeuristic(&rounding);

    CbcHeuristicGreedyCover heuristicGreedyCover(model);
    heuristicGreedyCover.setHeuristicName("greedy cover");
    heuristicGreedyCover.setWhereFrom(1);
    model.addHeuristic(&heuristicGreedyCover);

    CbcHeuristicGreedyEquality heuristicGreedyEquality(model);
    heuristicGreedyEquality.setHeuristicName("greedy equality");
    heuristicGreedyEquality.setWhereFrom(1);
    model.addHeuristic(&heuristicGreedyEquality);
#endif

    CbcHeuristicDiveCoefficient heuristicDiveCoefficient(model);
    heuristicDiveCoefficient.setWhen(3);
    heuristicDiveCoefficient.setHeuristicName("DiveCoefficient");
    heuristicDiveCoefficient.setDecayFactor(1);
    heuristicDiveCoefficient.setWhereFrom(493);
    model.addHeuristic(&heuristicDiveCoefficient);

#if 0
    CbcHeuristicRINS heuristicRINS(model);
    heuristicRINS.setFractionSmall(0.5);
    heuristicRINS.setHeuristicName("RINS");
    heuristicRINS.setDecayFactor(5);
    heuristicRINS.setWhereFrom(65289);
    heuristicRINS.setShallowDepth(0);
    model.addHeuristic(&heuristicRINS);

    CbcHeuristicLocal heuristicLocal(model);
    heuristicLocal.setFractionSmall(0.5);
    heuristicLocal.setHeuristicName("combine solutions");
    heuristicLocal.setSwitches(16);
    heuristicLocal.setSearchType(1);
    model.addHeuristic(&heuristicLocal);
#endif

    CbcCompareDefault compare;
    model.setNodeComparison(compare);
}

static ClpSimplex&
clp_model_of(CbcModel& model)
{
    return *(dynamic_cast<OsiClpSolverInterface*>(model.solver())->getModelPtr());
}

static void
configure_model_parameters(CbcModel& model)
{
    OsiSolverInterface* osiModel = model.solver();
    OsiClpSolverInterface* osiclpModel = dynamic_cast<OsiClpSolverInterface*>(osiModel);
    ClpSimplex* clpModel = osiclpModel->getModelPtr();
    
    model.setPrintFrequency(100);
    model.setSpecialOptions(66050);
    model.messageHandler()->setLogLevel(1);
    model.setMaximumCutPassesAtRoot(-100);
    model.setMaximumCutPasses(4);
    model.setMinimumDrop(0.000103333);
    
    clpModel->scaling(2);
    clpModel->defaultFactorizationFrequency();
    clpModel->setDualBound(1.0001e+08);
    clpModel->setPerturbation(50);

    osiclpModel->setSpecialOptions(1057);
    osiclpModel->setIntParam(OsiMaxNumIterationHotStart,100);
    osiclpModel->setHintParam(OsiDoReducePrint,true,OsiHintTry);
}

static bool
tighten_primal_bounds(CbcModel& model)
{
    ClpSimplex& simplex = clp_model_of(model);
    
    if (simplex.tightenPrimalBounds() != 0) 
    {
        return false;
    }
    else
    {
        simplex.dual();  // clean up
        return true;
    }
}


int 
main (int argc, const char *argv[])
{
    OsiClpSolverInterface solver1;
    int status = 1;
    
    status = solver1.readMps("test.mps", "");
    
    if (status) 
    {
        std::cout<<"Bad readMps "<<argv[1]<<std::endl;
        exit(1);
    }

    CbcModel model(solver1);

    std::cout << "After reading, the problem has "
              << solver1.getNumRows() << " rows, and "
              << solver1.getNumCols() << " columns."
              << std::endl;
    
    configure_cut_generators(model);
    configure_heuristics(model);
    configure_model_parameters(model);
    
    model.initialSolve();
    
    if (!tighten_primal_bounds(model))
    {
        std::cout << "\nInitial solve says, that the problem is infeasible." << std::endl;
    }
    else
    {
        // Hand coded preprocessing
        CglPreProcess process;
        Ptr<OsiSolverInterface> saveSolver(model.solver()->clone());
    
        // Tell solver we are in Branch and Cut
        saveSolver->setHintParam(OsiDoInBranchAndCut, true, OsiHintDo) ;
    
        // Default set of cut generators
        CglProbing generator1;
        generator1.setUsingObjective(1);
        generator1.setMaxPass(3);
        generator1.setMaxProbeRoot(saveSolver->getNumCols());
        generator1.setMaxElements(100);
        generator1.setMaxLookRoot(50);
        generator1.setRowCuts(3);
    
        // Add in generators
        process.addCutGenerator(&generator1);
        process.messageHandler()->setLogLevel(model.logLevel());

        std::cout << std::endl << "And now, for something completely different...\n"
                  << std::endl;

        OsiSolverInterface *solver2 = process.preProcessNonDefault(*saveSolver, 2, 10);
    
        // Tell solver we are not in Branch and Cut
        saveSolver->setHintParam(OsiDoInBranchAndCut,false,OsiHintDo);
    
        if (solver2)
            solver2->setHintParam(OsiDoInBranchAndCut,false,OsiHintDo);

        if (!solver2) 
        {
            std::cout << "Pre-processing says infeasible!" << std::endl;
            exit(1);
        } 
        else 
        {
            std::cout << "processed model has " << solver2->getNumRows()
                      << " rows, " << solver2->getNumCols()
                      << " columns and " << solver2->getNumElements()
                      << " elements" << solver2->getNumElements()<<std::endl;
        }
    
        // Comment was generated; not sure, why it applies here:
        // "we have to keep solver2 so pass clone"
        //
        // The problem is: if we use "solver2" directly (in assign_solver below),
        // then we get an access violation in the call to "process.postProcess()"
        // below. If we clone "solver2" (uncomment the line below and use the
        // appropriate call to assign_solver) then we get an access violation 
        // at the end of main, when the destructors are run and "solver2" tries
        // to delete its pointer.
        // 
        // Things work fine, if we use a plain pointer for "solver2", but this
        // seems to leak the memory on exit, and I am not comfortable with that,
        // given that the real application solves a lot of MIPs sequentially in
        // the same process.
        
        // Ptr<OsiSolverInterface> solver3(solver2->clone());   // Works until the
        // assign_solver(model, solver3);                       // end of main, then segfaults
        
        //assign_solver(model, solver2);                          // Segfaults in postProcess()
	OsiSolverInterface * solver3 = solver2->clone();
        model.assignSolver(solver3);
        model.initialSolve();
        model.branchAndBound();
    
        // For best solution
        int numberColumns = solver1.getNumCols();
    
        if (model.getMinimizationObjValue() < 1.0e50) 
        {
            process.postProcess(*model.solver());
        
            assign_solver(model, saveSolver);
            memcpy(model.bestSolution(), model.solver()->getColSolution(), numberColumns * sizeof(double));
            solver1.setColSolution(model.bestSolution());
        }
    }  

    std::cout << "\nDone" << std::endl;

    return 0;
}


More information about the Cbc mailing list