DEV Community

Cover image for NeoHaskell v0.4.0: Update with Concurrency Fixes and Architectural Improvements
Nick Tchayka for NeoHaskell

Posted on

NeoHaskell v0.4.0: Update with Concurrency Fixes and Architectural Improvements

After 3 weeks facing a wall of awful, I managed to fix this concurrency bug in the service worker architecture, where even though the user app loop was being executed properly, the exceptions weren't exiting the app. This critical fix, along with several other significant improvements, marks the release of NeoHaskell v0.4.0. Let's dive into the details of what's new and what's been fixed in this major update.

The Concurrency Bug: A Deep Dive

The core of the problem lay in our service worker architecture. While the main application loop was running as expected, any exceptions that occurred weren't properly propagated to cause the application to exit. This led to situations where the app would appear to be running, but was actually in a broken state.

The fix involved several steps:

  1. Implementing a shouldExit flag in the RuntimeState:
   type RuntimeState (event :: Type) =
     Record
       '[ "actionHandlers" := Action.HandlerRegistry,
          "actionsQueue" := Channel (Action event),
          "shouldExit" := Bool
        ]
Enter fullscreen mode Exit fullscreen mode
  1. Adding exception handlers to each worker thread that set this flag:
   let cleanup threadName exception = do
         print [fmt|[init] EXCEPTION: {toText exception}|]
         print [fmt|[init] {threadName} cleanup|]
         print "[init] Cleaning up"
         runtimeState |> RuntimeState.modify (\s -> s {shouldExit = True})
Enter fullscreen mode Exit fullscreen mode
  1. Modifying each worker to check this flag and exit cleanly when set:
   state <- RuntimeState.get runtimeState
   if state.shouldExit
     then do
       print "Exiting due to shouldExit flag"
       IO.exitSuccess
     else do
       -- Continue with normal operation
Enter fullscreen mode Exit fullscreen mode

This change ensures that when an exception occurs in any part of the application, all threads are notified and can shut down gracefully, allowing the application to exit properly.

Major Architectural Improvements

Service Architecture Refactoring

In this major update, we've completely overhauled the Service module, splitting it into smaller, more focused modules:

  • Service.Core: Core types and interfaces
  • Service.ActionWorker: Action processing
  • Service.EventWorker: Event handling and model updates
  • Service.RenderWorker: View rendering
  • Service.RuntimeState: Shared runtime state management

This significant refactoring improves code maintainability and makes the system easier to understand and modify, setting a new foundation for future development.

Service Architecture Overview

As part of our architectural improvements, we've refined the core structure of the NeoHaskell Service. To better illustrate these changes, let's examine a high-level diagram of the Service architecture:

architecture diagram

  1. ActionsQueue and EventsQueue: These channels facilitate communication between different parts of the system. The ActionsQueue handles incoming actions, while the EventsQueue manages events.

  2. StateRef: This is a concurrent variable that holds the current state of the application model.

  3. Workers:

    • ActionWorker: Processes actions from the ActionsQueue and can generate events.
    • RenderWorker: Handles rendering the current state of the application.
    • EventWorker: Processes events, updates the model, and can generate new actions.
  4. init, view, update (UserApp): Represents the user's application logic, which initializes the State and ActionsQueue, as well as rendering the state, and updating the state based on the events.

  5. Triggers: External sources that can generate events and feed them into the EventsQueue.

The interactions between these components form the core of the NeoHaskell Service:

  • The UserApp initializes the StateRef and ActionsQueue.
  • Triggers feed events into the EventsQueue.
  • The ActionWorker reads from ActionsQueue and writes to EventsQueue.
  • The EventWorker reads from EventsQueue, updates ModelRef, and can write to ActionsQueue.
  • The RenderWorker reads from StateRef to render the current state.

This architecture provides a clear separation of concerns, allowing for better management of state, actions, and events in NeoHaskell applications. It forms the foundation for the concurrency improvements and bug fixes implemented in this release.

By clearly defining the roles and interactions of each component, we've created a more robust and maintainable system. This structure allows for easier debugging, better performance, and more predictable behavior in complex applications.

The refinements in this architecture have directly contributed to resolving the concurrency issues mentioned earlier. By clearly defining the flow of data and the responsibilities of each worker, we've minimized the chances of deadlocks and race conditions, resulting in a more stable and reliable system.

Enhanced CLI Argument Parsing

The OptionsParser module has been renamed to Command and substantially expanded with new capabilities:

  • Support for parsing Path type arguments
  • Closer integration with the action system for seamless command handling
  • Improved error handling and user feedback

Other Key Improvements

Terminal Rendering Bug Fix

We resolved a critical issue where the terminal was left in an unusable state after the program exited. This was fixed by properly implementing Vty.shutdown:

(_, vty) <-
  Brick.customMainWithDefaultVty
    (Just eventChannel)
    brickApp
    model
Vty.shutdown vty
Enter fullscreen mode Exit fullscreen mode

Additional Improvements

  • A new ActionResult type in the Action module for more robust error handling
  • Introduction of the fmt quasi-quoter for text interpolation
  • New utility functions added to core modules like Array, Basics, and IO

Conclusion

NeoHaskell v0.4.0 represents a significant step forward in the project's development. The concurrency bug fix addresses a critical issue in the system, while the architectural improvements set the stage for more robust and maintainable code in the future.

These updates provide a stronger foundation for NeoHaskell applications and demonstrate our commitment to improving the language.

As we move forward, we welcome feedback and contributions from the community to help shape the future of NeoHaskell. This major release marks an important milestone, but our work continues as we strive to make NeoHaskell an even more powerful and user-friendly language.

Top comments (0)