Nesting try-catch Blocks in PHP

Published on October 13, 2017

I was recently presented with an interesting problem on a client's project. We have a Web application which, via an API endpoint, triggers work for a background process, and the background worker then updates the status record when the work is complete with the final URL where the Web application can retrieve the result (in this case, a dynamically generated PDF form). I've had a few cases where the worker process struck an error, but caused a conditon that was hard to diagnose.

The general structure is a daemonized command which executes in cycles (one cycle kicking off 1 second after the previous cycle completed). The entirety of the command logic is wrapped in a try-catch block so that errors are properly logged and the process can respawn. The problem that occurred was when one of the subsequent method calls failed, the exception was thrown back to the command logic, where it was caught and subsequently logged. The problem arises from the fact that the record which kicks off the unit of work is never updated with an error condition. So, a second later when the daemon checks for work again, it hits the same error, which it logs and then shuts down and is forced to respawn.

I still need to wrap the primary command logic in a try-catch, but I looked hard for a way to not nest a try-catch block in the subsequent function calls for each of the units of work it picked up on the current cycle. I ultimately settled on nesting the block, but noted a significant increase in memory usage by doing so.

We use PM2 to manage worker processes. This was the stack before the nested try-catch block was added:

┌───────────────────────┬────┬──────┬───────┬────────┬─────────┬────────┬─────┬───────────┬──────────┐
│ App name              │ id │ mode │ pid   │ status │ restart │ uptime │ cpu │ mem       │ watching │
├───────────────────────┼────┼──────┼───────┼────────┼─────────┼────────┼─────┼───────────┼──────────┤
│ worker.documentation  │ 3  │ fork │ 24431 │ online │ 0       │ 0s     │ 0%  │ 32.6 MB   │ disabled │
│ worker.events         │ 4  │ fork │ 24434 │ online │ 0       │ 0s     │ 0%  │ 33.2 MB   │ disabled │
│ worker.lsu            │ 2  │ fork │ 24430 │ online │ 0       │ 0s     │ 0%  │ 35.2 MB   │ disabled │
│ worker.qualifications │ 1  │ fork │ 24425 │ online │ 0       │ 0s     │ 0%  │ 38.3 MB   │ disabled │
└───────────────────────┴────┴──────┴───────┴────────┴─────────┴────────┴─────┴───────────┴──────────┘
 

This was the stack after the change:

┌───────────────────────┬────┬──────┬───────┬────────┬─────────┬────────┬─────┬───────────┬──────────┐
│ App name              │ id │ mode │ pid   │ status │ restart │ uptime │ cpu │ mem       │ watching │
├───────────────────────┼────┼──────┼───────┼────────┼─────────┼────────┼─────┼───────────┼──────────┤
│ worker.documentation  │ 3  │ fork │ 24431 │ online │ 0       │ 22s    │ 0%  │ 51.2 MB   │ disabled │
│ worker.events         │ 4  │ fork │ 24434 │ online │ 0       │ 22s    │ 0%  │ 52.5 MB   │ disabled │
│ worker.lsu            │ 2  │ fork │ 24430 │ online │ 0       │ 22s    │ 0%  │ 51.5 MB   │ disabled │
│ worker.qualifications │ 1  │ fork │ 24425 │ online │ 0       │ 22s    │ 0%  │ 51.4 MB   │ disabled │
└───────────────────────┴────┴──────┴───────┴────────┴─────────┴────────┴─────┴───────────┴──────────┘

The overall usage is acceptable (this runs on a separate server from the main Web application in production), but it's still a memory usage increase of ~34% to ~58% (depending on the process). I've had other senior developers at other organizations tell me that nesting try-catch blocks like this is a performance issue, but in this case it seems unavoidable. 

I'd be interested to hear from other PHP developers on how they handle this scenario. Are there any patterns that can alleviate the need for nesting the blocks? Is this just a necessity in certain situations?