iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🤔

Don't Hold onto context.Context

に公開

I updated my self-made packages for accessing book information over the weekend.

https://github.com/spiegel-im-spiegel/pa-api/releases/tag/v0.9.0
https://github.com/spiegel-im-spiegel/aozora-api/releases/tag/v0.3.0
https://github.com/spiegel-im-spiegel/openbd-api/releases/tag/v0.3.0

There are some breaking changes this time. Well, I don't think many people besides me use them, so the impact should be minimal.

I started creating these three packages about a year or two ago, but back then I was putting them together with a "just do it" attitude without thinking much about the behavior of the context package, and looking back recently, I felt they were a bit "clumsy."

Furthermore, after seeing the official blog post published shortly after the release of Go 1.16:

https://blog.golang.org/context-and-structs

I thought, "That's exactly right," and was considering when to fix it.

The article above basically says "do not hold onto context.Context as a struct field," which is also explicitly stated in the context package documentation.

Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it.

Since the reasons are explained in detail in the blog post above, I strongly recommend reading it at least once.

Now, this is totally an excuse, but since the purpose of the context package is:

Context provides a means of transmitting deadlines, caller cancellations, and other request-scoped values across API boundaries and between processes.

I hope you can understand the urge to include context.Context as part of the context information to be passed to other layers or domains orz

Coincidentally, at yesterday's reading event, the section on cancellation and event propagation using channel closure (Chapter 8.9 of 『The Go Programming Language』) came up, and I finally decided to get to work, thinking, "Is this a sign to fix it quickly?"

The reason channel closure can be used for cancellation events is that in Go, a channel blocks the waiting goroutine when it is unbuffered or empty, but the block is released (or the receive operation exits without waiting) the moment the channel is closed, even if nothing is sent. This happens concurrently and equally even when multiple goroutines are waiting on a single channel[1]. In other words, "closing a channel" can be "broadcast" as an event trigger.

And since Go 1.7, the context package has used this mechanism quite skillfully. From another perspective, if you don't implement context.Context with the "cancellation event propagation using channel closure" mechanism in mind, unexpected side effects may occur. For example, like the one shown in "Contexts and structs".

Also, I was inspired by a recently published article:

https://zenn.dev/imamura_sh/articles/retry-46aa586aeb5c3c28244e

In this article, there is a section titled "Check the termination of context.Context", and the content should make sense if you understand how context.Context works.

In my case, when I wrote a package for "Simple work of just fetching data from a URI", I (intended to) wrote it considering not to hold context.Context as a struct field, so I was able to handle it by replacing the HTTP client operations in my other self-made packages with this github.com/spiegel-im-spiegel/fetch package.

Well, that resulted in a breaking change, though. This is how we bit by bit pay off technical debt, right? (lol)

脚注
  1. Goroutines in Go have no priority. This means priority inversion, which is common in RTOS and such, does not occur. To put it another way, Go is not suitable for systems that require severe real-time processing. This was a topic of conversation during the reading event. ↩︎

GitHubで編集を提案

Discussion