-
Notifications
You must be signed in to change notification settings - Fork 366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New streaming backend #135
Open
flashmob
wants to merge
132
commits into
master
Choose a base branch
from
stream
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Open
fix tests
…_parser.go) - use a 4KB buffer to process the stream (io.CopyBuffer instead of io.Copy
- Add a new 'process' stream decorator
More comments.
- content type more flexible to white-space and missing charset param
- fix test
- if email header line has a parse error then ignore it then continue parsing
stream processor decorators have a new Shutdown function, switched to use this instead of Svc start developing background processing
…spatcher is started for each processor type - new ValidatingProcessor type - removed "gw" prefix form gateway config options
Open
background processing: borrow a new envelope and copy the existing protocol params to it.
gatweay: workerID is unique
4 done |
… the concrete value
backend processing debugging & added test
Still have a lot of small problems, but once that's fixed then real testing can start! |
- update some log messages to use structured logging - update tests to be aware of the new log format
envelope: changed queuedID to packed bytes and added a fmt.Stringer
mysql driver taking shape. |
envelopoe: added mime parse error to enveloper fixed tests
make ChunkPrefetchCount & ChunkMaxBytes configurable update comments to parseInfo
chunkPrefetchCountDefault configurable (add chunkPrefetchMax)
@flashmob Are you still planning to finish this PR? I see you have invested quite some time but then the motivation died down? 😄 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem
The current 'backend' processes the emails by buffering the entire message in memory, then applying each Processor using the decorator pattern. This approach works OK - most emails are never over 1MB anyway, and the buffers are recycled after processing, keeping them allocated for the next message, (being nice the garbage collector). However, some things can be done more efficiently - for example the message could be compressed while it's being saved on-the-fly. This is the main idea of 'streaming'.
Solution
The go Go way of streaming is to use the
io.Writer
andio.Reader
interfaces. What if we could use the current decorator pattern that we use for the backends, extend that by making the processors implement theio.Writer
interface? 🤔Yes, we can do just that. A little bit of background first: Did you know that the
io.Writer
way is usually a decorator pattern? When we make an instance of a writer, we usually pass some underlying writer to it, allowing us to wire multiple writers together. Some people call this pattern 'chaining'Normally, when using io.Writer, if you would like to create a chain, you need to manually wire them with a few lines of code. This solution takes it further, by allowing you to wire the Writers by configuration.
Technical Details
Each Writer is an instance of a
StreamDecorator
, it's a struct that implementsio.Writer
. Additionally, the struct contains two callback functionsOpen
andClose
, both could be set when theStreamDecorator
is being initialized, and called back at the start and end of the stream. TheOpen
callback is also used to pass down the*mail.Envelope
which can be used to keep the state for the email currently being processed.in gateway.go there's a new
newStreamStack
method that instantiates the StreamDecorator structs and wires them up.A new method was added to the
Backend
interfaceA new configuration option was also added to the config file:
stream_save_process
.The value is a string with the names of each StreamDecorator to chain, delimited by a pipe |.
This is how the io.Reader is passed from the DATA command down to the backend. The ProcessStream method calls all the
Open
methods on our writers, and then begins the streaming of data usingio.Copy
. At the end of the stream, it calls Close() on our decorators in the other they were wired.Examples
Perhaps the best way to understand this is to look at some example code.
There are 3 examples of
StreamDecorator
implementations in thebackends
dir:You will notice that each of the files contain a function that looks just like the
io.Writer
interface,without the
Write
keyword. I.eStreamProcessWith(func(p []byte) (int, error)
This is an anonymous function which is converted to an
io.Writer
when it is returned. Here is the codeof s_debug.go
The most important detail here is that the
sp
identifier refers to the nextio.Writer
in the chain.In other words,
sp
contains a reference to the underlying writer.(The
sd.Open
statement does nothing, it's just there here as an example / to be used as a template.)In the api_test.go file, there is a test called `TestStreamProcessor'. The writers are chained with the
following config setting:
"stream_save_process": "Header|compress|Decompress|debug"
Which means it will call the
Write
method on the Header first, and then down to each underlying writer in the stream.Todo: