Skip to content

erikwehrmann/channels-log-processor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 

Repository files navigation

Why This Project Exists

This repository exists to teach how Go thinks about concurrency, not just how to write code that happens to work.

Summary

This repository is a minimal, production-style Go project designed to teach the core concurrency primitives of the language:

  • Channels for communication and backpressure
  • Goroutines for concurrent execution
  • context.Context for cancellation and lifecycle management
  • Explicit composition instead of frameworks

The project intentionally avoids databases, ORMs, and third‑party libraries so you can focus entirely on how Go coordinates work.

At a high level, the system works like this:

  1. An HTTP endpoint (/log) receives requests
  2. Each request produces a unit of work
  3. Work is sent through a channel (no shared memory)
  4. A pool of workers processes work concurrently
  5. Results are aggregated and logged
  6. A single cancellation signal shuts everything down cleanly

This mirrors real-world Go services used in:

  • APIs
  • background job processors
  • event pipelines
  • message consumers

How to Run

go run .

In another terminal:

curl http://localhost:8080/log

Press Ctrl+C to observe graceful shutdown.

Core Mental Models

1. Why Channels Replace Locks

Locks protect memory. Channels protect behavior.

Locks answer the question:

“Who can access this data right now?”

Channels answer the question:

“Who owns this work now?”

When you send a value on a channel:

jobs <- job

You are saying:

“I am done with this value. Someone else owns it now.”

This eliminates:

  • Shared mutable state
  • Lock ordering bugs
  • Deadlocks caused by forgotten mutexes

Rule of thumb:

Don’t communicate by sharing memory; share memory by communicating.

2. Why Workers Should Never Be Force-Killed

Go assumes cooperative cancellation, not forceful termination.

Force-killing goroutines causes:

  • Partial work
  • Resource leaks
  • Corrupted state
  • Unpredictable shutdowns

Instead, workers listen for cancellation signals:

select {
case <-ctx.Done():
    return
case job := <-jobs:
    process(job)
}

This means:

  • Work finishes at safe boundaries
  • Goroutines exit cleanly
  • Shutdown is predictable

Rule of thumb:

Goroutines should stop because they are told to stop — not because they are killed.

3. Why Context Is Passed, Not Stored

context.Context is request-scoped.

It represents:

  • Cancellation
  • Deadlines
  • Request metadata

It is not:

  • Global state
  • Configuration
  • A service locator

Correct usage:

func handle(ctx context.Context, job Job) error

Incorrect usage:

var ctx context.Context // ❌

Contexts:

  • Flow downward through function calls
  • Are cancelled by parents
  • Should never outlive the request

Rule of thumb:

If a function does I/O or starts goroutines, it should accept a context.

4. Why Closing Channels Is a Signal, Not Data

Sending a value:

jobs <- job

Means:

“Here is one more piece of work.”

Closing a channel:

close(jobs)

Means:

“There will never be more work.”

This allows consumers to write:

for job := range jobs {
    process(job)
}

No special values. No magic flags. No protocol pollution.

Rule of thumb:

Closing a channel communicates lifecycle, not information.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages