Have you got some logs confirming you observation? run beats in foreground with -e -d 'logstash' and kill running logstash.
The window is supposed to shrink with every batch(window) that has failed. Originally, a batch not being send by publishWindowed, will not be handled by async handler. That is (*msgReg).fail() was not be executed. But this changed at some point. I think you're right, shrinkWindow is executed twice.
Good news is, with beats input being rewritten recently, I'm planning to remove the windowing support. The windowing was used to figure an ok-batch size so to not having to reconnect every so often or continuously fail with logstash being overloaded. The rewrite sports a keep-alive signal, making the dynamic windowing in output plugin superfluous.
2016/09/01 02:13:47.070020 async.go:138: DBG Try to publish 2 events to logstash with window size 10
2016/09/01 02:13:47.657564 async.go:114: DBG 2 events out of 2 events sent to logstash. Continue sending
2016/09/01 02:13:47.657596 async.go:138: DBG Try to publish 2 events to logstash with window size 2
2016/09/01 02:13:47.660661 async.go:114: DBG 0 events out of 2 events sent to logstash. Continue sending
2016/09/01 02:13:47.660685 async.go:68: DBG close connection
It seems that window size shrinks twice, from 10 to 5, and then from 5 to 2, between two publishing.
Nice. You're totally right!!! Great you found this!
Also the eventsNotAcked counter might be wrong, as batch is reported to be failed twice. Once during error handling and once by callback. Shrinking window + updating counter should only happen in (*msgRef).dec(). Also (*msgRef).fail() is not reporting the failed send. Maybe the log message should be moved too.
You want to create a PR fixing this bug (I see you signed CA already)? Given you did dig through async IO-code finding it, I'd rather see your name next to the fix then mine. Honor to whom honor is due