December Adventure

    Floods

    It’s been a few weeks since my March week of December Adventuring came to an abrupt end, and I though it was time to write about it: on March 20 a Kona low—a cyclone by any other name—brought floods to our small town of Waialua here on the North Shore of O'ahu, forcing us to evacuate by foot through waist-high water. Thankfully, we’re safe, there was no recorded loss-of-life (despite hearing some truly terrifying stories from friends and neighbours), and we’ve personally been very lucky—water didn’t enter our house. That said, many lost their houses, crops, and possessions and, while it starts to look more normal here again, there’s a lot of quiet work rebuilding and reestablishing livelihoods. No one wants to see rain again for a long while.

    The community has been amazing, on the day and beyond: Sarah and I were given coffee and breakfast by neighbours and then evacuated, first by a local construction firm in the bucket of a bulldozer, and then by some lads in their jacked truck1. And seeing everyone contributing to clean up and rebuilding has been amazing—while the state has struggled find its place in all of this, I continue to hear epic tales of the efforts of those living here.

    If you’d like to contribute to the disaster relief efforts here, there are many ways you can do so, including Our Hawai'i, the Lāhui Foundation, and Refurnish Waialua. There are also many individual GoFundMe campaigns; here’s a few posted on the wall of Mele Mele, our local coffee shop, if you’d like to donate:


    Needless to say, I didn’t take many photos, but here’s a few that capture just a little of our experience…

    Our day began, like many, with emergency alerts at about 2am, when we promptly discovered the car sitting in few feet of water.

    By the time we decided to leave, the sun was coming up and the beach road past our house had become a river—waist-deep at points, and chest-deep heading the other direction into town.

    It was particularly absurd to see the bus stop under water—there was no way we were catching a bus this morning.

    By the time we were making our way out of Waialua some hours later, the damage was already incredibly clear, with whole buildings having been washed away at Otake Camp.

    Whither plptools?

    Needless to say, I wasn’t able to continue my December Adventure work on plptools, and getting back to things has been a slow process. I managed to put together a new proof-of-concept command line—plpbackup—before things ground to a halt:

    I’ll be continuing to work on this over the coming weeks and I hope to merge it in the not-too-distant future.


    1. I shall never again say a bad word about jacked trucks. 

    December Adventure' March 17-18

    Concurrency, Linking, and Self-Hosted Daemons

    Continuing my Psion connectivity themed March December Adventure, I persisted with the process of addressing legacy threading issues in plptools—it’s important that we have a stable foundation on which we can build future functionality, and it’s still my intuition it’s better to build on top of what we have than start again.

    Foundational Work

    The plptools architecture centralises all the complexity in ncpd, the daemon responsible for implementing the Psion Link Protocol (PLP) and exposing different Psion-side server end-points to PC-side TCP clients. This makes the clients relatively simple at the cost of some gnarly internal code which is, unsurprisingly, where all our problems lie.

    An overly simplified overview of that looks something like this:

    ---
    config:
      class:
          hideEmptyMembersBox: true
    ---
    classDiagram
    direction TB
    
    class NCPSession
    class NCP
    class Link
    class DataLink
    class LinkChannel
    class SocketChannel
    
    NCPSession "1" --> "1" NCP : ncp_
    NCP "1" --> "1" Link : link_
    NCP "1" --> "n" SocketChannel : channelPtr
    NCP "1" --> "1" LinkChannel : lChan
    Link "1" --> "1" DataLink : dataLink_
    

    On first blush, this seems complex, but there’s quite a bit that needs to happen and the functionality is relatively well compartmentalized:

    • NCPSession—provides APIs to manage the full daemon life cycle
    • NCP—multiplexes channels to Psion-side servers, pairing them to connected PC-side TCP clients
      • LinkChannel—a special channel for communicating with the Psion to manage the overall connection
      • SocketChannel—PC-side TCP client end-point
    • Link—manages connection establishment and packet sequencing, transmission, and retransmission
    • DataLink—frames and un-frames messages, and writes and reads the serial port

    Most of my effort over the last few days has been focused on DataLink—the goal is to ensure we have a robust core before focusing on other aspects of the architecture. Looking at the existing code, my theory is that this was originally written to be single-threaded and threading was added after-the-fact, with no locking at all. This has given me a wonderful opportunity to refresh my memory of C++’s take on mutexes, locks, and condition variables. The introduction of locks has a significant impact on how we shut down ncpd: the various worker threads were relying on PTHREAD_CANCEL_ASYNCHRONOUS which can stop them at any point during their execution, potentially leaving locks held and resulting in deadlock. With locks, we have to explicitly manage thread cancellation. My PR for these changes has evolved over the past few days, and I think it’s nearly there. Thanks must go to Alex and Fabrice, the other plptools maintainers, for stoically reviewing and testing my many changes, and keeping me honest during the process.

    Self-Hosted Daemons

    With the various thread-safety improvements in place, I took another crack at adding a self-hosted daemon to plpftp to allow it to be run without necessitating a separate ncpd instance. This involves moving the daemon classes into ‘libplp’ to allow all the plptools commands to share them and, unfortunately, doing so still resulted in a near-immediate crash in that looked like a double-free in BufferStore, our buffer convenience wrapper.

    Fairly sure I wasn’t seeing a multi-threading issue this time around, I kept digging and, after altogether too long, realized the crash was a result of the free function itself being NULL. Unsurprisingly, when runtime fundamentals like this are missing, you get some incredibly strange and misleading crashes and call stacks. Thankfully the fix was easy: I had failed to link libgnu; a failure that macOS silently ignores, leaving all function pointers NULL. 🤦

    With everything finally working as expected, I was able to make the change I’d intended three days prior, adding a simple call to conditionally start a daemon (NCPSession) instance if the serial port was passed as an argument to plpftp:

    Semaphore *sem = new Semaphore();
    NCPSession *session = nullptr;
    if (serialDevice) {
        session = new NCPSession(
            sockNum,
            115200,
            host,
            serialDevice,
            false,
            0,
            [](void *context, bool connected, int version) {
                static_cast<Semaphore *>(context)->signal();
            },
            sem);
        session->start();
        sem->wait();
    }
    

    As is always the way in software, this ‘feature’ work proved the easiest bit and, with the foundations in place, this worked first time:

    December Adventure' March 16

    A Slow Start

    I started the week with grand plans of adding a small feature a day to one of plptools or Reconnect—perhaps a backup command, TCP support for connecting to MAME, improvements to the file transfer, or incremental backups in Reconnect. One day in, it’s clear I’m certainly not going to manage one a day.

    Usability Improvements

    plptools adheres to a very Linux mindset, comprising multiple command line apps, each providing a distinct piece of functionality. These all rely on a central daemon—ncpd—for managing the serial port and connections to a Psion. While it’s a great system that allows you to run multiple tools in parallel (e.g., installing a program and managing your files at the same time), it does require users to run ncpd in addition to whatever tool they wish to use. For example, I run the following two commands to connect to my Series 3mx:

    1. Start ncpd to connect to the Psion:

      ncpd -s /dev/cu.usbserial-A9DA9DOF -d
      
    2. Run plpftp to browse and transfer files:

      plpftp
      

    Inspite of the clear benefits of the architecture, unless you’re planning to run the daemon all the time (which Reconnect does but we don’t have a great story for yet in plptools), this can feel pretty heavyweight just to copy a file. I also have a theory that having to understand the plptools architecture sufficiently to know to run ncpd is a pretty big barrier to getting started. With that in mind, I plan to allow the different plptools apps to self-host the daemon if you specify a serial port. This will reduce the above to:

    plpftp -s /dev/cu.usbserial-A9DA9DOF
    

    Doing this means using reusing the NCPSession class from ncpd in plpftp, necessitating moving it into libplp (the library that’s shared between all the plptools CLI apps). This should be simple, but moving it left me with an app that segfaults as soon as the Psion connects. My working theory is that I’ve subtly changed the timing or object life cycle in a way that exposes existing race conditions. With that, I returned to the exercise of gently tidying the codebase and the work of adding thread-safety to ncpd—an incredibly nuanced process with a codebase as long-in-the-tooth as plptools.