Skip to content

Large BulkInsertOptimizedAsync does not cancel using CancellationToken #636

@DerMagereStudent

Description

@DerMagereStudent

When inserting millions of records using BulkInsertOptimizedAsync (event traces of the BPIC17) I noticed that the operation does not cancel even though properly using and passing a CancellationToken. Here a minimal reproducible example:

Docker DB:

version: "3.8"

services:
  db:
    image: postgres:16-alpine
    container_name: bulk-insert-cancel-test-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: db
    ports:
      - "45432:5432"

Code:

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.EntityFrameworkCore;

public class TableItem {
    public Guid Id { get; set; }
    public string Name { get; set; } = default!;
}

public class TestContext : DbContext {
    public DbSet<TableItem> Table { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)    {
        if (!optionsBuilder.IsConfigured) {
            var connectionString = "Host=localhost;Port=45432;Database=db;Username=postgres;Password=postgres";

            optionsBuilder.UseNpgsql(connectionString);
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder) {
        modelBuilder.Entity<TableItem>(entity => {
            entity.HasKey(e => e.Id);
            entity.Property(e => e.Id).ValueGeneratedNever();

            entity.Property(e => e.Name)
                .IsRequired()
                .HasMaxLength(256);
        });
    }
}

public static class ProgramBulkInsert {
    public static async Task Main(string[] args) {
        const int totalItems = 5_000_000;

        Console.WriteLine("Ensuring database is created...");
        await using (var setupContext = new TestContext()) {
            await setupContext.Database.EnsureCreatedAsync();
            await setupContext.Database.ExecuteSqlRawAsync($"TRUNCATE TABLE \"{nameof(TestContext.Table)}\";");
        }

        Console.WriteLine($"Generating {totalItems:N0} entities in memory...");
        var items = Enumerable.Range(1, totalItems)
            .Select(i => new TableItem {
                Id = Guid.CreateVersion7(),
                Name = $"Item #{i}"
            })
            .ToList();

        Console.WriteLine("Entities generated.");

        using var cts = new CancellationTokenSource();
        var token = cts.Token;

        var stopwatch = new Stopwatch();
        _ = Task.Run(async () => {
            await Task.Delay(TimeSpan.FromSeconds(2));
            Console.WriteLine(">>> Requesting cancellation");
            cts.Cancel();
            stopwatch.Start();
        });

        Console.WriteLine("Starting BulkInsertOptimizedAsync with cancellation token...");

        try {
            await using var context = new TestContext();

            _ = await context.BulkInsertOptimizedAsync(items, token);

            stopwatch.Stop();

            Console.WriteLine("BulkInsertOptimizedAsync COMPLETED without throwing.");

            await using var verifyContext = new TestContext();
            var countInDb = await verifyContext.Table.LongCountAsync();
            Console.WriteLine($"Rows in DB after operation: {countInDb:N0}");
        }
        catch (OperationCanceledException) {
            stopwatch.Stop();
            Console.WriteLine("BulkInsertOptimizedAsync threw OperationCanceledException (was cancelled).");
            Console.WriteLine($"Elapsed since Cancel: {stopwatch.Elapsed}");
        }
        catch (Exception) {
            stopwatch.Stop();
            Console.WriteLine("BulkInsertOptimizedAsync threw some other exception:");
        }

        Console.WriteLine($"CancellationToken.IsCancellationRequested = {token.IsCancellationRequested}");
    }
}

Packages:

<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
  <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
  <PackageReference Include="Z.EntityFramework.Extensions.EFCore" Version="10.105.0" />
</ItemGroup>

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions