SQLitePCL.raw icon indicating copy to clipboard operation
SQLitePCL.raw copied to clipboard

Native assembly loading outside of application (e_sqlite3.dll)

Open yojagad opened this issue 5 years ago • 24 comments

We have a hosted service that uses an overridden AssemblyLoadContext to load native assemblies. One of our customers moved from Microsoft.EntityFrameworkCore.Sqlite 2.2.6 to 3.3.1. The former seemed to be using SQLPCLRaw.bundle_green and we were able to see our custom AssemblyLoadContext being invoked for probing and thereby loading e_sqlite3.dll.

This does not seem to be the case for the latter though (3.3.1 and the app was on .net core 3.1) which is using SQLPCLRaw.bundle_e_sqlite3 and it seems to keep throwing errors saying e_sqlite3.dll was not found because it did not use our custom AssemblyLoadContext to probe for native assemblies. Interestingly, targeting the app to use netstandard 2.0 seem to go back to using our custom AssemblyLoadContext.

Are there plans to improve the native dependency loading behavior by engaging the CLR probing especially in case of assembly load failures like the above?

cc: @fabiocav

yojagad avatar May 06 '20 00:05 yojagad

I think NativeLibrary.Load() bypasses AssemblyLoadContext. It's probably worth filing an issue on dotnet/runtime to see if this is a bug or by design.

bricelam avatar Jul 16 '20 23:07 bricelam

That's correct. With the current implementation, that is the case, which causes problems in any environment providing isolation with ALC (including Azure Functions)

fabiocav avatar Jul 16 '20 23:07 fabiocav

My setup is Azure Functions V1 - .net framework 4.8 Middle layer is .net standard 2.0 with reference to Microsoft.Data.Sqlite Exception thrown while debugging in VS2019 is it can't load e_sqlite3.dll Any workarounds would be awesome so we can keep moving forward. Thanks!

aderderian avatar Jul 29 '20 21:07 aderderian

The workaround I would investigate would be to use the DllImport e_sqlite3 SQLitePCLRaw provider instead of the dynamic one.

This would involve manual steps to do the things that are usually done for you by the bundle packages.

So instead of SQLitePCLRaw.bundle_whatever, add a reference to SQLitePCLRaw.core (to get the core library) and SQLitePCLRaw.provider.e_sqlite3 (to get the provider implementation) and SQLitePCLRaw.lib.e_sqlite3 (to get the e_sqlite3 library itself).

Without the bundle, you'll need to initialize SQLitePCLRaw yourself by creating a provider and calling SetProvider.

SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3());

I don't actually know if this will work. I'm just thinking that if the problem here is that NativeLibrary is failing because of AssemblyLoadContext things, then perhaps regular old DllImport would work.

Cc @bricelam in case he sees me saying something dumb here.

ericsink avatar Jul 30 '20 00:07 ericsink

The DllImport should work. We could validate this in Azure Functions.

For context (@aderderian), we're only tracking this for Functions 3.0 at this point.

FYI, @yojagad

fabiocav avatar Jul 30 '20 00:07 fabiocav

Thanks guys I will try now. I am using Azure Functions V1 right because I need to be running .net 4.8 for an old (eb) assembly to work. All seems to work fine in Functions V3 if I paste the e_sqlite3.dll into the bin in V3. No dice in V1.

Do I add these references and do the import in my Azure Function or in my .NET Standard layer?

https://github.com/Azure/Azure-Functions/issues/1664

aderderian avatar Jul 30 '20 00:07 aderderian

I think those references and the init call go in your Azure Function project, but I should admit I have very little experience with Azure Functions.

ericsink avatar Jul 30 '20 01:07 ericsink

image

When I call it from Azure Functions V1 on app Startup. I added all those references.

aderderian avatar Jul 30 '20 01:07 aderderian

That seems like a bitness mismatch. Are you running in a 32 or 64 bit app? Can you make sure it matches the native dependency you're deploying?

fabiocav avatar Jul 30 '20 01:07 fabiocav

Yes. I just switched it to force 64 bit and it now got past the : SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3());

Same error though when I try and run a SqlCommand using Dapper and Microsoft.Data.Sqlite (5.0.0-preview.7.20365.15)

image

aderderian avatar Jul 30 '20 01:07 aderderian

You appear to still have a reference somewhere to a bundle package. I'm guessing it's probably coming in through Microsoft.Data.Sqlite?

Try Microsoft.Data.Sqlite.Core instead?

ericsink avatar Jul 30 '20 01:07 ericsink

Here is my .NET Standard data layer project :

image

And this is my Azure Function :

image

aderderian avatar Jul 30 '20 01:07 aderderian

Yeah, that's what I'm talking about. You have a reference to Microsoft.Data.Sqlite, which is bringing in (as a dependency) SQLitePCLRaw.bundle_e_sqlite3, and you don't want that.

Try changing it to Microsoft.Data.Sqlite.Core instead.

ericsink avatar Jul 30 '20 01:07 ericsink

Did that. Oddly now the WebApp that also uses this shared .NET Standard library will now not start with the error below. My Azure Function and the webapp share a data layer for some information. The error below is actually happening outside of all this, so no clue how it is event getting over to that now.

An unhandled exception occurred while processing the request. TypeLoadException: Method 'sqlite3_stmt_isexplain' in type 'SQLitePCL.SQLite3Provider_dynamic_cdecl' from assembly 'SQLitePCLRaw.provider.dynamic_cdecl, Version=2.0.2.669, Culture=neutral, PublicKeyToken=b68184102cba0b3b' does not have an implementation. SQLitePCL.Batteries_V2.DoDynamic_cdecl(string name, int flags)

TargetInvocationException: Exception has been thrown by the target of an invocation. System.RuntimeMethodHandle.InvokeMethod(object target, object[] arguments, Signature sig, bool constructor, bool wrapExceptions)

TypeInitializationException: The type initializer for 'Microsoft.Data.Sqlite.SqliteConnection' threw an exception. Microsoft.Data.Sqlite.SqliteConnection..ctor()

TargetInvocationException: Exception has been thrown by the target of an invocation. System.RuntimeTypeHandle.CreateInstance(RuntimeType type, bool publicOnly, bool wrapExceptions, ref bool canBeCached, ref RuntimeMethodHandleInternal ctor, ref bool hasNoDefaultCtor)

aderderian avatar Jul 30 '20 01:07 aderderian

provider.dynamic_cdecl is not supposed to be around anymore. Somewhere it is still sneaking in. Maybe some other dependency. Maybe try a clean and full rebuild.

ericsink avatar Jul 30 '20 01:07 ericsink

So strange, so the WebApp has the following references. I use OrchardCore (which I love and have used for years). They use Sqlite as well. Could the bundle there be conflicting with this somehow?

image

aderderian avatar Jul 30 '20 02:07 aderderian

It's a tough predicament because at least in Functions V3, adding the e_sqlite3.dll to the bin solves it. And we only use SQlite for local dev anyways. But we need Functions V1 for legacy .NET 4.8 projects and everything works, except this. I also tried System.Data.Sqlite and the same thing happened but it was System.Data.Interop that was the missing dll.

aderderian avatar Jul 30 '20 02:07 aderderian

"Could the bundle there be conflicting with this somehow?"

The workaround we've been discussing requires calling SetProvider(). If a bundle_e_sqlite package is getting into the project where we are trying to use that workaround, then yes, that's a problem.

Another thing you could try is adding a call to FreezeProvider() just below the SetProvider() call:

SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3());
SQLitePCL.raw.FreezeProvider();

If your SetProvider() call happens first, then the FreezeProvider() call should prevent others from messing it up.

ericsink avatar Jul 30 '20 03:07 ericsink

I can't really modify the core guts of OrchardCore and YesSQL, and my Azure functions layer shares a data layer with one of the custom modules I have built into the Orchard pipeline.

Not really sure how to resolve it. A lot of moving parts here.

aderderian avatar Jul 30 '20 11:07 aderderian

Did you try the FreezeProvider call?

ericsink avatar Jul 30 '20 14:07 ericsink

I did not. I am not sure where I would insert that in the pipeline of the web app starting. I can't modify the underlying assembly references. It is getting a little non-maintainable, but I really appreciate the help. I think the main issue is Functions V1 won't easily let me just add the dll in the bin.

aderderian avatar Jul 30 '20 14:07 aderderian

Link to related issue:

dotnet/runtime#13819

ericsink avatar Aug 03 '20 14:08 ericsink

SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_e_sqlite3()); SQLitePCL.raw.FreezeProvider();

@ericsink I put your two lines in the constructor of my class and it fixed the problem immediately!

TheCollegedude avatar Sep 26 '23 17:09 TheCollegedude

This issue is likely related to #553

ericsink avatar Sep 26 '23 17:09 ericsink