Skip to content
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

"Disk I/O error" on .Net 9 Blazor WebAssembly App and IDBFS #613

Open
peerem opened this issue Nov 15, 2024 · 10 comments
Open

"Disk I/O error" on .Net 9 Blazor WebAssembly App and IDBFS #613

peerem opened this issue Nov 15, 2024 · 10 comments

Comments

@peerem
Copy link

peerem commented Nov 15, 2024

After updating to .Net 9 we get "Disk I/O error". A read and write test with a simple text file in IDBFS was successful in .Net 9. See this thread:

#472 (comment)

@ericsink
Copy link
Owner

I need LOTS more info. A sample that reproduces the problem. Etc.

@peerem
Copy link
Author

peerem commented Nov 15, 2024

I can't upload files from my work computer, but I can copy the code as text and describe the small modifications to a standard Blazor app template. Is that enough for you? I'll put it in step by step, it's not much.

@ericsink
Copy link
Owner

Is that enough

I don't know, but it's moving in the right direction.

@peerem
Copy link
Author

peerem commented Nov 15, 2024

  1. Create project from template:
    Screenshot 2024-11-15 191118
    Screenshot 2024-11-15 191213

  2. Create "files.js" in "wwwroot" folder:

function mount(dotNet)
{
    // Mount db
    const dbFolder = '/data';
    Blazor.runtime.Module.FS.mkdir(dbFolder);
    Blazor.runtime.Module.FS.mount(Blazor.runtime.Module.FS.filesystems.IDBFS, {}, dbFolder);
    Blazor.runtime.Module.FS.syncfs(true, async (err) =>
    {
        if (dotNet)
        {
            const hasError = err != null;
            await dotNet.invokeMethodAsync('MountFinished', hasError);
        }
    });
}

function sync()
{
    // Sync db
    Blazor.runtime.Module.FS.syncfs(async (err) =>
    {

    });
}

  1. Content of csproj-file:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

    <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <EmccExtraLDFlags>-lidbfs.js</EmccExtraLDFlags>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" PrivateAssets="all" />
        <PackageReference Include="sqlite-net-sqlcipher" Version="1.9.172" />
        <PackageReference Include="SQLitePCLRaw.bundle_e_sqlcipher" Version="2.1.10" />
        <PackageReference Include="SQLitePCLRaw.core" Version="2.1.10" />
        <PackageReference Include="SQLitePCLRaw.lib.e_sqlcipher" Version="2.1.10" />
        <PackageReference Include="SQLitePCLRaw.provider.e_sqlcipher" Version="2.1.10" />
    </ItemGroup>

</Project>

  1. Add this to "index.html" in the "head" section:

<script src="files.js"></script>

  1. Content of "Home.razor":
@page "/"
@using System.IO
@using SQLite

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

@code {
    private DotNetObjectReference<Home> dotNet;

    // Injections
    [Inject]
    private IJSRuntime JsRuntime { get; set; }

    protected override async void OnAfterRender(bool firstRender)
    {
        base.OnAfterRender(firstRender);

        // Mount IDBFS
        if (firstRender)
        {
            // Create .NET reference
            dotNet = DotNetObjectReference.Create(this);

            // Mount
            await JsRuntime.InvokeVoidAsync("mount", dotNet);
        }
    }

    [JSInvokable]
    public async void MountFinished(bool hasError)
    {
        if (!hasError)
        {
            // Test
            string filename = "/data/Test.txt";

            // Create if not exists
            if (File.Exists(filename))
                Console.WriteLine("File already exists.");
            else
            {
                Console.WriteLine("Create file.");

                // Write file
                File.WriteAllText(filename, "Hello persistent filesystem.");

                // Sync with IndexedDB
                await JsRuntime.InvokeVoidAsync("sync");
            }

            // Read
            if (File.Exists(filename))
                Console.WriteLine(File.ReadAllText(filename));

            // Test database
            string databaseFile = "/data/Test.sqlite";

            // Db exists?
            bool dbExists = File.Exists(databaseFile);

            // Open or create database
            SQLiteConnection connection = new SQLiteConnection(new SQLiteConnectionString(databaseFile, SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, true, "secret"));
            connection.Execute("PRAGMA cipher_compatibility=3");

            // If new create a table
            if (!dbExists)
            {
                // Create test table
                connection.Execute(
                    @"CREATE TABLE IF NOT EXISTS TEST (
                        ID           TEXT NOT NULL,
                        NAME         TEXT NOT NULL,
                        TEST         TEXT,
                        PRIMARY KEY (ID, NAME))");
            }

            // Sync with IndexedDB
            await JsRuntime.InvokeVoidAsync("sync");
        }
    }
}

If you then run this, you will get the "disk i/o error" when executing the "create table" sql.

@ericsink
Copy link
Owner

I suggest changing from sqlite-net-sqlcipher to sqlite-net base, and then add a call to SQLitePCL.Batteries.Init().

Also, if you have the bundle_e_sqlcipher package, you don't need to have entries for the other three SQLitePCLRaw packages.

Did the same (or equivalent) test work under .NET 8?

Do you get the same results with this test if you change from SQLCipher to regular SQLite?

@peerem
Copy link
Author

peerem commented Nov 15, 2024

In .Net 8 this code runs without any problems (with a few adjustments in the Javascript).

I have now changed my project file as follows:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

    <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <EmccExtraLDFlags>-lidbfs.js</EmccExtraLDFlags>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
        <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" PrivateAssets="all" />
        <!--
        <PackageReference Include="sqlite-net-sqlcipher" Version="1.9.172" />
        <PackageReference Include="SQLitePCLRaw.bundle_e_sqlcipher" Version="2.1.10" />
        -->
        <PackageReference Include="sqlite-net-base" Version="1.9.172" />
        <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
    </ItemGroup>

</Project>

Then I added "SQLitePCL.Batteries.Init();" before creating the connection. The basic version works, the cypher version generates the following exception:

SQLite.SQLiteException: disk I/O error
at SQLite.SQLite3.Prepare2(sqlite3 db, String query)
at SQLite.SQLiteCommand.Prepare()
at SQLite.SQLiteCommand.ExecuteNonQuery()
at SQLite.SQLiteConnection.Execute(String query, Object[] args)
at BlazorApp4Sqlite.Pages.Home.MountFinished(Boolean hasError) in C:\Temp\Temp3\Test\BlazorApp4Sqlite\Pages\Home.razor:line 67
at System.Threading.Tasks.Task.<>c.b__128_1(Object state)
at System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.ThreadPool.BackgroundJobHandler()

Of course we want to continue using the cypher version.

@ericsink
Copy link
Owner

OK, so this appears to be a problem with the build of e_sqlcipher for wasm. The e_sqlcipher builds are unsupported, but I'll take a look at the build configuration and see if I see an easy or obvious problem.

@peerem
Copy link
Author

peerem commented Nov 15, 2024

Thank you.

@peerem
Copy link
Author

peerem commented Nov 21, 2024

Any news about the problem?

@ericsink
Copy link
Owner

Nothing significant yet. I verified that my build system is doing a net9 build for e_sqlcipher, which I wasn't sure about, but I haven't found time to dig deeper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants