This is just a bunch of tips and tricks we keep in mind while developing Oragono. If you wanna help develop as well, they might also be worth keeping in mind!
The master
branch should be kept relatively runnable. It might be a bit broken or contain some bad commits now and then, but the pre-release checks should weed those out before users see them.
For either particularly broken or particularly WiP changes, we work on them in a develop
branch. The normal branch naming is develop+feature[.version]
. For example, when first developing ‘cloaking’, you may use the branch develop+cloaks
. If you need to create a new branch to work on it (a second version of the implementation, for example), you could use develop+cloaks.2
, and so on.
Develop branches are either used to work out implementation details in preperation for a cleaned-up version, for half-written ideas we want to continue persuing, or for stuff that we just don’t want on master
yet for whatever reason.
irctest
]() over it to make sure nothing’s severely broken.-unreleased
from the version number in irc/constants.go
.git tag v0.0.0 -m "Release v0.0.0"
(0.0.0
replaced with the real ver number).stable
branch.Once it’s built and released, you need to setup the new development version. To do so:
irc/constants.go
, update the version number to 0.0.1-unreleased
, where 0.0.1
is the previous release number with the minor field incremented by one (for instance, 0.9.2
-> 0.9.3-unreleased
)."Setup v0.0.1-unreleased devel ver"
.Unreleased changelog content
## Unreleased
New release of Oragono!
### Config Changes
### Security
### Added
### Changed
### Removed
### Fixed
vendor/
The vendor/
directory holds our dependencies. When we import new repos, we need to update this folder to contain these new deps. This is something that I’ll mostly be handling.
To update this folder:
cd
to Oragono folderdep ensure -update
cd vendor
"Updated packages"
cd ..
"vendor: Updated submodules"
This will make sure things stay nice and up-to-date for users.
Fuzzing can be useful. We don’t have testing done inside the IRCd itself, but this fuzzer I’ve written works alright and has helped shake out various bugs: irc_fuzz.py.
In addition, I’ve got the beginnings of a stress-tester here which is useful: https://github.com/DanielOaks/irc-stress-test
As well, there’s a decent set of ‘tests’ here, which I like to run Oragono through now and then: https://github.com/DanielOaks/irctest
It’s helpful to enable all loglines while developing. Here’s how to configure this:
logging:
-
method: stderr
type: "*"
level: debug
To debug a hang, the best thing to do is to get a stack trace. The easiest way to get stack traces is with the pprof listener, which can be enabled in the debug
section of the config. Once it’s enabled, you can navigate to http://localhost:6060/debug/pprof/
in your browser and go from there. If that doesn’t work, try:
$ kill -ABRT <procid>
This will kill Oragono and print out a stack trace for you to take a look at.
Oragono involves a fair amount of shared state. Here are some of the main points:
client.Send
appends the message to a queue, which is then processed on a separate goroutine. It is always safe to call client.Send
.In consequence, there is a lot of state (in particular, server and channel state) that can be read and written from multiple goroutines. This state is protected with mutexes. To avoid deadlocks, mutexes are arranged in “tiers”; while holding a mutex of one tier, you’re only allowed to acquire mutexes of a strictly higher tier. The tiers are:
Channel.stateMutex
.ChannelManager.RWMutex
.Server.rehashMutex
, which prevents rehashes from overlapping.There are some mutexes that are “tier 0”: anything in a subpackage of irc
(e.g., irc/logger
or irc/connection_limits
) shouldn’t acquire mutexes defined in irc
.
We are using buntdb
for persistence; a buntdb.DB
has an RWMutex
inside it, with read-write transactions getting the Lock()
and read-only transactions getting the RLock()
. This mutex is considered tier 1. However, it’s shared globally across all consumers, so if possible you should avoid acquiring it while holding ordinary application-level mutexes.
We support a lot of IRCv3 specs. Pretty much all of them, in fact. And a lot of proposed/draft ones. One of the draft specifications that we support is called “labeled responses”.
With labeled responses, when a client sends a label along with their command, they are assured that they will receive the response messages with that same label.
For example, if the client sends this to the server:
@label=pQraCjj82e PRIVMSG #channel :hi!
They will expect to receive this (with echo-message also enabled):
@label=pQraCjj82e :nick!user@host PRIVMSG #channel :hi!
They receive the response with the same label, so they can match the sent command to the received response. They can also do the same with any other command.
In order to allow this, in command handlers we don’t send responses directly back to the user. Instead, we buffer the responses in an object called a ResponseBuffer. When the command handler returns, the contents of the ResponseBuffer is sent to the user with the appropriate label (and batches, if they’re required).
Basically, if you’re in a command handler and you’re sending a response back to the requesting client, use rb.Add*
instead of client.Send*
. Doing this makes sure the labeled responses feature above works as expected. The handling around PRIVMSG
/NOTICE
/TAGMSG
is strange, so simply defer to irctest’s judgement about whether that’s correct for the most part.