Skip to content

Race conditions, thread-safety issues, and code quality in Developer Balance sample #748

@NirmalKumarYuvaraj

Description

@NirmalKumarYuvaraj

Problems

  1. ClearTables() was declared as async void, meaning LoadSeedDataAsync() could not await it. This created a race condition where seed data inserts could execute before table drops completed. Any exception inside async void would also crash the app silently (unhandled on the thread pool), and the original error was swallowed via Console.WriteLine.
  2. SeedDataService.ClearTables() called Task.WhenAll on all four repositories concurrently. However, ProjectRepository.DropTableAsync() internally already calls _taskRepository.DropTableAsync() and _tagRepository.DropTableAsync(). This caused the same tables to be dropped from two concurrent call sites simultaneously, risking SQLite locking errors (SQLITE_BUSY) and non-deterministic behavior.
  3. The file was named TaskRespository.cs (note "Resp" instead of "Repo") while the class inside was correctly named TaskRepository. This typo made the file hard to discover and inconsistent with the rest of the codebase.
  4. All four repositories used a simple bool _hasBeenInitialized flag to guard Init(). Under concurrent async access, multiple callers could pass the if (_hasBeenInitialized) return; check before any of them set the flag to true, resulting in multiple table creation attempts running in parallel.
  5. ListAsync() issued separate SQL queries for each project's tags and tasks. With N projects this results in 2N+1 total queries, which is inefficient and a well-known anti-pattern.
  6. The ProjectsTags class was defined but never referenced anywhere in the codebase. The join table is managed entirely via raw SQL in TagRepository. The class also had an ID field not present in the actual database schema (which uses a composite primary key).
  7. In ListAsync(int projectID), one SQL parameter was added without the @ prefix:
  8. MainPageModel: returned null when project was null → caller receives a null Task, which can throw NullReferenceException if awaited.
  9. JsonContext was the only class in the entire template with no namespace declaration, making it a global-namespace type. This was inconsistent and could cause resolution ambiguity.
  10. DisplaySnackbarAsync created a CancellationTokenSource that was never disposed and had no timeout, meaning it would hold resources indefinitely.

Steps to Reproduce

  1. Clone the repo
  2. Run the developer balance app

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions