Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

This use of GC.SuppressFinalize() doesn't feel right

I have been having some issues with utilizing a vendor library where occasionally an entity calculated by the library would be null when it should always have valid data in it.

The functioning code (after debugging the issue with the vendor) is roughly as follows:

    Task.Factory.StartNew(() => ValidateCalibration(pelRectRaw2Ds, crspFeatures, Calibration.Raw2DFromPhys3Ds));

    .....

    private void ValidateCalibration(List<Rectangle> pelRectRaw2Ds, List<List<3DCrspFeaturesCollection>> crspFeatures, List<3DCameraCalibration> getRaw2DFromPhys3Ds)
    {
        var calibrationValidator = new 3DCameraCalibrationValidator();

        // This is required according to vendor otherwise validationResultsUsingRecomputedExtrinsics is occasionally null after preforming the validation
        GC.SuppressFinalize(calibrationValidator);

        3DCameraCalibrationValidationResult validationResultUsingOriginalCalibrations;
        3DCameraCalibrationValidationResult validationResultsUsingRecomputedExtrinsics;
        calibrationValidator.Execute(pelRectRaw2Ds, crspFeatures, getRaw2DFromPhys3Ds, out validationResultUsingOriginalCalibrations, out validationResultsUsingRecomputedExtrinsics);

        Calibration.CalibrationValidations.Add(new CalibrationValidation
            {
                Timestamp = DateTime.Now,
                UserName = Globals.InspectionSystemObject.CurrentUserName,
                ValidationResultUsingOriginalCalibrations = validationResultUsingOriginalCalibrations,
                ValidationResultsUsingRecomputedExtrinsics = validationResultsUsingRecomputedExtrinsics
            });
    }

The validation process is a fairly time consuming operation so I hand it off to a Task. The problem I had was that originally I did not have the call to GC.SuppressFinalize(calibrationValidator) and when the application was run from a Release build, then the out parameter validationResultsUsingRecomputedExtrinsics would be null. If I ran the application from a Debug build (either with or without the Debugger attached) then validationResultsUsingRecomputedExtrinsics would contain valid data.

I don't fully understand what GC.SuppressFinalize() has done in this situation, or how it has fixed the problem. Everything I can find regarding GC.SuppressFinalize() is that it is used when implementing IDisposable. I can't find any use of it in "standard" code.

How/why does the addition of the call to GC.SuppressFinalize(calibrationValidator) fix this problem?

I understand that without intimate knowledge of the internals of the vendor library, it might not be possible to know for sure, but any insight would help.

The application is compiled with VS2012, targeting .NET 4.0. That vendor library requires that the useLegacyV2RuntimeActivationPolicy="true" option is specified in app.config.

This is the justification I received from the vendor:

The SuppressFinalize command makes sure that the garbage collector will not clean something up “early”. It seems like for some reason your application was sometimes having the garbage collector get a bit zealous and clean up the object before you were truly done with it; it is almost certainly scope related and possibly due to the multi-threading causing confusion on the scope of the calibrationValidator. Below is the response I got from Engineering.

Because the variable was created in the local scope, and that function runs in the background thread, Garbage Collection runs in the main thread, and it seems that the Garbage collection is not smart enough in handling multi-thread situations. Sometimes, it just releases it too early (internal execution of validator not finished yet, and still needs this variable).

like image 814
Dave Nay Avatar asked Mar 22 '13 00:03

Dave Nay


1 Answers

This is in all likelihood a hack to solve a premature garbage collection problem. Not uncommon with unmanaged code, typical in camera applications. It is not a healthy hack, good odds that this will cause a resource leak because the finalizer doesn't execute. Wrappers for unmanaged code almost always have something to do in the finalizer, it is very common that they need to release unmanaged memory.

At issue is that the calibrationValidator object can be garbage collected while the unmanaged code is running. Having another thread in your program makes this likely since that other thread can be allocating objects and trigger a GC. This is very easy to miss by the owner of the code while testing, either by never having tested it while using multiple threads or just not getting lucky enough to trigger a GC at the wrong time.

The proper fix on your end is to ensure that the jitter marks the object in use past the call so that the garbage collector won't collect it. You do so by adding GC.KeepAlive(calibrationValidator) after the Execute() call.

like image 178
Hans Passant Avatar answered Oct 11 '22 15:10

Hans Passant