-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Added IDisposable support for several classes #4939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
#region IDisposable Support | ||
private bool _disposed; | ||
|
||
private void Dispose(bool disposing) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 This is the legacy IDisposable
pattern. The new way to implement this is moving this implementation code directly into IDisposable.Dispose()
. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weren't there two cases to be handled before? The one that calls Dispose(true) from from IDisposable.Dispose() and the other that calls Dispose(false) from the finalizer? (Or someplace else...I am not quite sure). How would that be handled?
In reply to: 396132625 [](ancestors = 396132625)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the other that calls Dispose(false) from the finalizer
Finalizers are not allowed under the new dispose pattern. Since there are no finalizers, there are no calls to Dispose(false)
. Since there are no calls to dispose false, there is no need to add complexity by defining the method. #Resolved
@@ -15,7 +16,7 @@ namespace Microsoft.ML.Data | |||
/// This class represents a data loader that applies a transformer chain after loading. | |||
/// It also has methods to save itself to a repository. | |||
/// </summary> | |||
public sealed class CompositeDataLoader<TSource, TLastTransformer> : IDataLoader<TSource> | |||
public sealed class CompositeDataLoader<TSource, TLastTransformer> : IDataLoader<TSource>, IDisposable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 By making the class disposable when IDataLoader<TSource>
is not disposable, it will be easy for a consumer to fail to dispose of the object. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't change interfaces now that they are shipped. Right now, when customers discover they have a memory leak, we don't have a solution. With this method at least we can tell them to add a Dispose call.
Is there a better approach we can consider?
In reply to: 396132696 [](ancestors = 396132696)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Every case that I reviewed locally required breaking API changes. Default interface methods might be an option for .NET Core targets, but those are completely unsupported on .NET Framework. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the component GA? Or can we still make a breaking change? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All public interfaces are frozen. This would be one of those.
In reply to: 396753025 [](ancestors = 396753025)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All public interfaces are frozen. This would be one of those.
There may be a difference between interfaces released as GA and the preview releases. I'm not sure which one this falls under.
/cc @eerhardt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDataLoader is a core interface. It is part of the GA set.
In reply to: 396760924 [](ancestors = 396760924)
test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs
Outdated
Show resolved
Hide resolved
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@@ -463,6 +471,7 @@ public void TensorFlowTransformInputOutputTypesTest() | |||
} | |||
Assert.False(cursor.MoveNext()); | |||
} | |||
(tfModel as IDisposable).Dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(tfModel as IDisposable).Dispose(); [](start = 12, length = 35)
(tfModel as IDisposable)?.Dispose();
public void TensorFlowImageClassification(ImageClassificationTrainer.Architecture arch) | ||
{ | ||
if (NotCentOS7FactAttribute.IsCentOS7()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsCentOS7 [](start = 40, length = 9)
as discussed pleas also skip in Ubuntu as this also hangs on Ubuntu like below:
https://dev.azure.com/dnceng/public/_build/results?buildId=556713&view=logs&j=28859320-f5de-51e0-1fd2-7bea8c11cf7a&t=28348391-228f-5cbf-8fcd-3272a0755d32
https://dev.azure.com/dnceng/public/_build/results?buildId=550567&view=logs&j=5aa5c7df-492a-5eaf-973a-62b7b0f0ee3b&t=ffdbd604-f3e2-5332-cf61-c8dd00799b47
https://dev.azure.com/dnceng/public/_build/results?buildId=549005&view=logs&j=28859320-f5de-51e0-1fd2-7bea8c11cf7a&t=28348391-228f-5cbf-8fcd-3272a0755d32
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -38,13 +39,15 @@ public CompositeDataLoader(IDataLoader<TSource> loader, TransformerChain<TLastTr | |||
|
|||
Loader = loader; | |||
Transformer = transformerChain ?? new TransformerChain<TLastTransformer>(); | |||
_disposed = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bool
s are initialized to false
by the runtime. There is no need to re-initialize them to false
here.
_disposed = true; | ||
} | ||
|
||
void IDisposable.Dispose() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you explicitly implementing IDisposable
? Instead, you can just have a public void Dispose()
method here.
The reason to not explicitly implement an interface is so users of the class can just call Dispose()
as necessary. They don't need to cast to IDisposable
to call it.
if (_disposed) | ||
return; | ||
|
||
(_bindable as IDisposable)?.Dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ISchemaBindableMapper
is internal, so if you want you can make it inherit from IDisposable
without a breaking change.
@@ -154,6 +154,8 @@ public void TensorFlowTransforCifarEndToEndTest2() | |||
Assert.Equal(0, prediction.PredictedScores[0], 2); | |||
Assert.Equal(0, prediction.PredictedScores[1], 2); | |||
Assert.Equal(1, prediction.PredictedScores[2], 2); | |||
|
|||
transformer.Dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use the using
statement in all these tests. It will make this cleaner.
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement
@@ -389,7 +385,7 @@ public void TensorFlowTransformInputOutputTypesTest() | |||
|
|||
var inputs = new string[] { "f64", "f32", "i64", "i32", "i16", "i8", "u64", "u32", "u16", "u8", "b" }; | |||
var outputs = new string[] { "o_f64", "o_f32", "o_i64", "o_i32", "o_i16", "o_i8", "o_u64", "o_u32", "o_u16", "o_u8", "o_b" }; | |||
var tfModel = mlContext.Model.LoadTensorFlowModel(modelLocation); | |||
using var tfModel = mlContext.Model.LoadTensorFlowModel(modelLocation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consistency/user-expectations:
How will a user know which model types and prediction engines are disposable? Should we convert all models and prediction engines to be disposable?
Seems it should be an all-or-none for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it should be all or none for consistency. If we go for none, then we need to make the interfaces inherit from IDisposable and we can't do that now without a breaking change. If for "all" then the Dispose() calls for most would become no-ops and only Tensorflow (right now) needs a real Dispose() call.
This seems like one of those cases that we will need to fix whenever we do a v2 release and can take breaking changes. Until then, I am okay with documenting for the tensorflow related transformers.
In reply to: 396761653 [](ancestors = 396761653)
@@ -1185,10 +1189,10 @@ public void TensorFlowSentimentClassificationTest() | |||
// For explanation on how was the `sentiment_model` created | |||
// c.f. https://github.com/dotnet/machinelearning-testdata/blob/master/Microsoft.ML.TensorFlow.TestModels/sentiment_model/README.md | |||
string modelLocation = @"sentiment_model"; | |||
var pipelineModel = mlContext.Model.LoadTensorFlowModel(modelLocation).ScoreTensorFlowModel(new[] { "Prediction/Softmax" }, new[] { "Features" }) | |||
using var pipelineModel = mlContext.Model.LoadTensorFlowModel(modelLocation).ScoreTensorFlowModel(new[] { "Prediction/Softmax" }, new[] { "Features" }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar changes may need to be made in the AutoML code, its use in Model Builder, and in CodeGen. Otherwise it will continue to leak in those components.
Todo: In a follow-up PR -- if needed add a using or call dispose() for TF models and TF prediction engines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have updated the docs and samples. I could not figure out easily where the corresponding fixes need to be bade in AutoML, Model Builder and CodeGen. @LittleLittleCloud, can you please help with that?
In reply to: 396764702 [](ancestors = 396764702)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM from the AutoML side.
May want to file an issue for follow up work on the AutoML API & CodeGen fronts in this repo, an issue in the dotnet/machinelearning-samples repo for follow up work on the TF and the AutoML API samples; and another in the dotnet/machinelearning-modelbuilder.
(update) Should create an issue for updating docs to educate users when they have to dispose and when they do not.
We have had a lot of instability in the Tensorflow tests. At least one of the issues has to do with memory leaks from ImageClassificationTrainer which holds references to the Tensorflow session and graphs. In order to fix this I have added IDisposable support for several of the classes involved in this scenario and fixed up the tests to call Dispose at the end of the tests.