Archive for the ‘Open Source Software’ Category

Interesting Node sleep() Function

Wednesday, October 26th, 2022

NodeJS

Today I had a reason to look at some Node issues with async processing, and ran across these two little functions that are interesting, but quite deadly. They pause the Node Runtime for the specified number of milliseconds, or seconds, and this is nice - when you have to have a delay, but all Node processing stops. This means all the async calls won't get processed, either.

  function msleep(n) {
    Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
  }
 
  function sleep(n) {
    msleep(n*1000);
  }

Simple. Easy. But not really what I was looking for. 🙂

Found a Nice Async Batching Library

Tuesday, October 18th, 2022

NodeJS

Yesterday, I was doing a little work, and noticed that I was getting a lot of connection resets on a service that has been flawless for more than 18 months, but to be fair, the load has been rising, and after digging into the cause, it appeared that the issue was overloading the Client with so many requests, it just failed.

Typically, a client will apply back-pressure on the caller to make sure that things don't get to this point, or they will queue the requests in memory so that they will be processed, in turn, as they arrived. I'm not exactly sure what's happening, the developers of the Client are looking at this, but I needed to find something to ease the load, and so I found asyncBatch().

Let's say I had the following code:

  const balances = (await Promise.all(companies
    .map(async c => {
      const bal = await minimumDueForCompany(user, c)
      if (bal?.success && !isNil(bal?.interestDue) && bal.billDate === today) {
        bal.company = c
        return bal
      }
      return undefined
    })))
    .filter(bal => bal !== undefined)

we're running through all the items in the companies array, and for each, we are calling minimumDueForCompany() and then checking a few things, and then filtering on those that we want to see. Simple.

But if we have more than 200 elements in the companies array, and the minimumDueForCompany() employs several database queries, we could get to the point of launching more than a thousand hits at nearly the same time. If this is a background task, this might be able to starve some more important tasks with all the database aork.

A batching solution was needed. And so I went looking.

asyncBatch() follows much the same style as the Promise.all(), it just takes the values as arguments: the array, the function, and the batch size:

  const asyncBatch = require('async-batch').default
 
  const balances = (await asyncBatch(companies,
    async c => {
      const bal = await minimumDueForCompany(user, c)
      if (bal?.success && !isNil(bal?.interestDue) && bal.billDate === today) {
        bal.company = c
        return bal
      }
      return undefined
    }, 2))
    .filter(bal => bal !== undefined)

With a batch size of 2, we'll start simply, and let the background task take a little longer, while preserving the more immediate user-facing calls can have priority access.

Put this in and things are working better. It's not a perfect solution, and we still need to have the Client improved, but it gets around the two problems: Flooding the database when the use-case doesn't require it... and Failures on the Client to handle the flood. We can fine-tune the batch size later.

UPDATE: it turned out that the library launched all the work in an initial Promise.all() so it really wasn't batching the work as I'd expected. So I wrote my own using the chunk library:

  const chunk = require('chunk')
 
  /*
   * We need a function that will batch the equivalent of:
   *
   *   const resp = await Promise.all(arr.map(itm => fcn(itm)))
   *
   * but do it in batches, so that when we get a large workload, we don't
   * overwhelm the system. This is that function. The first argument is the
   * array to process, the second is the async function, that takes one
   * argument, and the last is the batch size that defaults to a reasonable
   * value.
   */
  const asyncBatch = async (arr, fcn, batchSize = 4) => {
    const ans = []
    for (const b of chunk(arr, batchSize)) {
      const blk = await Promise.all(b.map(async itm => await fcn.apply(null, [itm])))
      ans.push(...blk)
    }
    return ans
  }

This works exactly as expected, working on n of the elements at a time, and then moving to the next batch. Much cleaner.

Adding Let’s Encrypt Certs to Nginx

Thursday, October 13th, 2022

Linode

This morning I had some time and wanted to finish up the work of getting my Cloud VM running Ubuntu 22.04 working just fine as a development box - including inbound webhooks from vendors, and calls from apps like HTTPbot on my iPad Pro. The key was that I needed to be able to install and configure nginx to forward all port 443 traffic to port 6543, and that also meant getting the nginx server to be listening on port 443 with a legit certificate.

Turns out, it wasn't as bad as I thought it might be. 🙂

Starting with my Ubuntu 22.04 install, I added the packages I was going to need, based on this blog post on the nginx site.

  $ sudo apt-get -y install --no-install-recommends nginx certbot python3-certbot-nginx

Once these are installed, we could set the server_name in the nginx config:

  $ sudo /etc/nginx/sites-enabled/default

and update the server_name line to be:

  server_name mybox.mydomain.com;

and then we can get the initial certificate from Let's Encrypt and register a new email account with them with:

  $ sudo certbot --nginx -d mybox.mydomain.com -d mydomain.com

and the second -d argument is for an additional domain for the certificate. I didn't need it, so I just had the one -d pair on my certbot command.

After this, we edit the config file again, updating the port 443 section's location specification with:

  location / {
    # forward all HTTPS traffic to port 6543
    proxy_set_header  X-Forward-For $remote_addr;
    proxy_set_header  Host $http_host;
    proxy_pass        "http://127.0.0.1:6543";
  }

and then verify the nginx config with:

  $ sudo nginx -t

and then tell nginx to reload the config with:

  $ sudo nginx -s reload

At this point, the box is answering HTTPS traffic, and forwarding it on to the Node service at port 6543. Excellent. 🙂

In order to refresh the Let's Encrypt Certificate on time, let's add a simple crontab entry:

  $ crontab -e

and then have the entries:

  # run all the commands on Bash not Bourne Shell
  SHELL=/bin/bash
  # send all the mail to my main account
  MAILTO=bob@mydomain.com
 
  # check the Let's Encrypt certificate each dat at noon UTC
  0 12 * * *   sudo /usr/bin/certbot renew --quiet

And that should do it.

Upgraded Postgres to 14.4 on Homebrew

Thursday, August 11th, 2022

PostgreSQL.jpg

Nothing major, but I did notice that 14.4 was the stable release of Postgres, and so I decided it was time to upgrade to the latest from Homebrew. And it really is very easy (again):

  $ brew upgrade postgresql

and then after all the downloading and installing, we have:

  $ psql --version
  psql (PostgreSQL) 14.4

Then we are good to go! 🙂

Interestingly, this time I didn't have to run:

  $ brew services restart postgresql

for when I did, it told me it was already running. Nice - it restarted on it's own. 🙂

Temurin 11 is on Homebrew

Tuesday, May 24th, 2022

Homebrew

This morning I decided to see if I could get the AdoptOpenJDK 11, now called Temurin 11, going on my M1Max MacBook Pro. In the past, they have had the AdoptOpenJDK 11 on Homebrew, but it was Intel, and I didn't want to bring in Rosetta 2 for any Clojure work, so I was willing to wait. I read on Twitter from the Eclipse Foundation that they had placed Java 11 and 8 for Apple Silicon on Homebrew, so why not?

It turns out, it's not bad at all:

  $ brew tap homebrew/cask-versions
  $ brea install --cask temurin11

and it's ready to go. Then I can use my setjdk function to switch between JDK 11 and 17 on my laptop, which is nice when there are some issues I've had to deal with in the past. Don't know when I'll need this again.

Sadly, the architecture for JDK 8 is still Intel:

  $ brew info temurin8
  temurin8: 8,332,09
  https://adoptium.net/
  Not installed
  From: https://github.com/Homebrew/homebrew-cask-versions/blob/HEAD/Casks/temurin8.rb
  ==> Name
  Eclipse Temurin 8
  ==> Description
  JDK from the Eclipse Foundation (Adoptium)
  ==> Artifacts
  OpenJDK8U-jdk_x64_mac_hotspot_8u332b09.pkg (Pkg)

that last line is the kicker: x64... so it goes. Still... I have JDK 11 for Apple Silicon now, that's good. 🙂

Upgraded Postgres to 14.2 on Homebrew

Wednesday, April 20th, 2022

PostgreSQL.jpg

This morning I was looking into the Ubuntu 20.04 to 22.04 LTS upgrades, and decided that it was probably a good time to see about the latest version of Postgresql for my laptop. Thankfully, it's a minor version upgrade from 14.0 to 14.2, and this is something that Homebrew can do quite nicely:

  $ brew upgrade postgresql
  $ brew services restart postgresql

and then after all the downloading and installing, we have:

  $ psql --version
  psql (PostgreSQL) 14.2

Then we are good to go! 🙂

I haven't had a need to use the local Postgres server a lot in the last year or so - using a Google Cloud instance for a while, but it's nice to have all the client support, and to have something local as that's still the best insurance to off-the-grid development.

Enjoyed Digging Through PostGraphile Code

Monday, January 10th, 2022

TypeScript

Over the weekend, I had a problem with a project at work that was using PostGraphile and I was having an issue with the Postgres URL that I was feeding to the configuration of PostGraphile:

    postgraphile(
      process.env.PLATTER_POSTGRES_URL,
      'public',
      {
        watchPg: false,
        graphiql: true,
        enhanceGraphiql: true,
        graphqlRoute: '/graphql/v2',
        graphiqlRoute: '/graphiql/v2',
        eventStreamRoute: '/graphql/v2/stream',
        appendPlugins: [
          require('@graphile-contrib/pg-simplify-inflector'),
          require('postgraphile/plugins').TagsFilePlugin,
        ]
      }
    )

The process.env.PLATTER_POSTGRES_URL was generated by a call to the Platter service, and it turned out to be cached, and stale, and as the source is Let's Encrypt, it needs to be updated monthly. The cache had been running for more than a month, and that left an old, expired, certificate for the site, in memory.

It was easy enough to have them restart the cache, getting to that point... well... 🙂 that was a little more involved. The first issue was that with PostGraphile, used as a Library, had the feature that if it could not generate the initial schema for the GraphQL engine, it would exit the process, and effectively crash the service.

I wanted to protect the service, so the first order of business was to update the way I was using PostGraphile to allow me to capture the failure, and just disable the GraphQL engine until we could get this problem figured out. Thankfully, that was as easy as adding a function to the retryOnInitFail option:

    postgraphile(
      process.env.PLATTER_POSTGRES_URL,
      'public',
      {
        watchPg: false,
        graphiql: true,
        enhanceGraphiql: true,
        graphqlRoute: '/graphql/v2',
        graphiqlRoute: '/graphiql/v2',
        eventStreamRoute: '/graphql/v2/stream',
        appendPlugins: [
          require('@graphile-contrib/pg-simplify-inflector'),
          require('postgraphile/plugins').TagsFilePlugin,
        ],
        retryOnInitFail: (err, att) => {
          log.error(`[PostGraphQL] Error on initialization: '${err}' with ${att}
                     attempts. Bailing out on PostGraphQL.`)
          return false
        }
      }
    )

At this point, we had time to figure out what the issue was. I was going to dig into the PostGraphile code, and Alex, at Platter, was going to look at it from his end. Which brings me to the point... the PostGraphile TypeScript code is very nice. Yes, there are a lot of packages/modules, and the organization is clean, but I'd have done it the same way - easy separation of responsibilities. The code is exceptionally clean... well considered and implemented. The more I dug into it, the more impressed I was with it as a project.

I thought it might be an issue with their code - an asset they are loading that has an expired certificate, so I filed that on GitHub, and had a wonderful exchange with the maintainer of the project. As I dug in more, and worked to repeat the issue on a much more limited basis, the back and forth with the maintainer was very helpful... just exactly what you'd hope to encounter - but often don't.

It was the most unexpected, wonderful, experience I'd had in months. It renewed my faith in Open Source again. 🙂

The Nasty Log4j Business

Monday, December 20th, 2021

log4j.jpg

It's been a wild couple of weeks for the log4j team... I mean, the problem with a logger is that you don't really want to limit it, and adding the url handlers probably seemed like a great idea at the time, but once they started to be used, it was understandably hard to drop support for them. And then the exploit hit.

It's just one of those nearly universal components of JVM systems that is being supported by volunteers, and trying to thread the needle between keeping as much of the functionality as they can... while restricting the vulnerability to something that can't be exploited. It's clearly not easy, as they've had at least three releases of the 2.x codebase to try and correct the vulnerability, and each time, there seems to be more there is to do.

This is certainly going to shift how some open source teams function... it's great to be the author, or maintainer of something as used as log4j, but to have this kind of attention... well... I'm sure it's not what they were hoping for this Christmas. 🙂

macOS 12 Monterey Dropped PHP

Monday, November 1st, 2021

php.jpg

I knew it was coming... they warned me with macOS 11 Big Sur... but it was still a bit of a surprise this morning to reconfigure Apache 2 to use userdir, which macOS Monterey doesn't enable by default, and undoes if you have it already configured. That's OK, it's not a huge deal to turn it back on, but the big news was the complete loss of PHP.

I haven't used it in many years, but it was the one tool that shipped with macOS that I could talk to Postgres, and script, but hey... things change, and they did warn me. 🙂

I guess it would be nice to have something like Node automatically handled, but then that would likkely clash with the Node devs and the nodenv installs, etc. So I'll live with Apache 2, and the userdir, and then just serve up static content on port 80, and leave the other stuff for the development environments.

A Bit Sad about Mosh and Agent Forwarding

Monday, October 25th, 2021

Blink

I did a little digging over the past few days to see what's new in the latest release of Blink (v14.0.2) - the terminal/shell for iOS and iPadOS. It's a nice tool - has even more features than I initially thought. It's really a nice subset of a Unix shell without needing to connect to any other hosts. But that's not really the point of this dig... I was hoping that they had implemented SSH Agent Forwarding in mosh connections so that I'd be able to use git on the remote machines. If you don't forward the SSH key, then you have to have them on the remote hosts, and ssh-add them there. That's not ideal for me, as it opens up the location of the key to a somewhat untrusted host.

I read the release notes and it seems they have re-written the SSH Agent component, and yet they didn't get very specific about the Mosh improvements, so I did a little more digging on the mosh-client code itself, and it seems that there's a bit of a disagreement about including Agent Forwarding in mosh due to security reasons. The conveneince of using git and SSH keys for git operations means that most folks want to have the key forwarding. And you can do it in simple ssh connections.

But for mosh, eventhough it's built on ssh, it seems they don't want to add it. Odd. But hey... it's their code, it's their choice, and that's why I have the workflow where I do the commits on my iPad, and use Blink with mosh just to run the code. It would be nice to have SSH Agent Forwarding, but the durability of mosh trumps the need for agent forwarding... so I'll just stick to what I have now.

But it sure would be nice... maybe they'll figure it out.