-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Use safe handles for SQLite interop #43361
Conversation
src/Workspaces/Core/Portable/Storage/SQLite/v1/Interop/SafeSqliteHandle.cs
Outdated
Show resolved
Hide resolved
protected override bool ReleaseHandle() | ||
{ | ||
raw.sqlite3_close(_wrapper); | ||
return true; |
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.
worth doc'ing if this is corrct wrt exceptions?
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 updated this to return false
when the result is not Result.OK
src/Workspaces/Core/Portable/Storage/SQLite/v1/Interop/SafeSqliteStatementHandle.cs
Outdated
Show resolved
Hide resolved
src/Workspaces/Core/Portable/Storage/SQLite/v1/Interop/SqlConnection.cs
Outdated
Show resolved
Hide resolved
src/Workspaces/Core/Portable/Storage/SQLite/v1/Interop/SqlConnection.cs
Outdated
Show resolved
Hide resolved
src/Workspaces/Core/Portable/Storage/SQLite/v1/Interop/SafeSqliteChildHandle`1.cs
Outdated
Show resolved
Hide resolved
protected override bool ReleaseChildHandle() | ||
{ | ||
raw.sqlite3_blob_close(Wrapper); | ||
return true; |
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.
- so, do all subclasses return true? if so, is there value in teh bool?
- should we be looking at the return values of the native method we're calling?
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.
so, do all subclasses return true?
Currently, yes. false
can be returned to indicate an error which is reported by an MDA during debugging.
if so, is there value in teh bool?
I kept it as close to ReleaseHandle
as possible (same return type with the same meaning).
should we be looking at the return values of the native method we're calling?
If one of the return values means error, we could return false
for that case. I'm neutral on this; the return value isn't used for anything aside from the debugger.
src/Workspaces/Core/Portable/Storage/SQLite/v1/Interop/SafeSqliteHandle`1.cs
Outdated
Show resolved
Hide resolved
|
||
if (result != Result.OK) | ||
{ | ||
handle.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.
this personally confuses me. the impl of sqlite3_open_v2 specifically gives back a bogus handle in the case of failure. so in that case, disposing it just serves to do no work. To me, it seems preferable to just return a handle?
. client then knows they have to check that, and deal with the null case (which certainly wouldn't need to be disposed). Or, it succeeds, and you have a real handle you cangrab and pass along.
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 personally confuses me. the impl of sqlite3_open_v2 specifically gives back a bogus handle in the case of failure.
This precisely follows the behavior that would occur if the P/Invoke signatures were originally written to operate on safe handles instead of IntPtr
(which is the preferable way to write the signatures since .NET 2.0).
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.
that's fine. i would personally prefer docs here then. because when i do the computation/data-flow in my head it basically indicates a smell here: i.e. i'm making a call that is certain to do nothing. if that's appropriate, we should at least doc. That way when i'm reading this 3 years from now i realize this is intentional and isn't just a line of code i'll throw out when doing cleanup :)
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.
Added docs
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 don't see the docs. can you push?
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.
They are in de40b56
src/Workspaces/Core/Portable/Storage/SQLite/v1/Interop/SqlConnection.cs
Outdated
Show resolved
Hide resolved
I like this a lot more :) just a few minor thoughts/suggestions on potential cleanup. Thanks! |
So, from the recent issue, it seems like the finalizer actually serves a good purpose (although it's clearly very unpleasant to have to investigate). if we move to this mode, should we at least make the storage service have a similar finalizer to catch clients that are forgetting to dispose it? |
I generally do not believe this matters. If someone feels otherwise, we can always catch this with performance traces and other tools. Another way to view this is: it's easy for us to maintain "mostly" deterministic release of resources, even without the finalizer. The finalizer only ever caught super edge cases, and those edge cases were never significant enough that it would have been a problem to just allow the finalizer to deal with them silently. Trying to get fancy with stopping every last case is more problematic than any benefit it would provide. |
I guess i find that somewhat unsatisfying. We had bugs where the system was being misused. The system properly was telling us: there's a leak, this is bad.
To me that's just papering over the issue (i.e. the original bug still remains, we're just unaware of it). Very specifically, these finalizers are intentionally not trying to make up for misbehaving code, but to call attention to it so that they can be made correct. I personally view that as a highly desirable property of the system, especially one that wraps a data-storage component. |
@CyrusNajmabadi There are a few ways a user could validate this cleanup even with safe handles, e.g. so it could run during integration tests. The obvious one would be adding a finalizer to the
Prior to .NET Core, |
internal sealed class SafeSqliteStatementHandle : SafeSqliteChildHandle<sqlite3_stmt> | ||
{ | ||
public SafeSqliteStatementHandle(SafeSqliteHandle sqliteHandle, sqlite3_stmt? wrapper) | ||
: base(sqliteHandle, wrapper?.ptr ?? IntPtr.Zero, wrapper) |
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.
should the ?? IntPtr.Zero
just happen in the base class instead of all the derived ones?
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 ptr
property isn't defined in an interface, and it seemed weird(er) to pass down IntPtr?
.
This commit back-ports dotnet#43361 to dev16.6.
No description provided.