Skip to content

Commit 524143c

Browse files
committed
ToxicStub allow to unblocking write to Output
It could happen when Link has no reciever and there is some packets in buffer. It produces deadlock. Dynamic test with latest version of toxiproxy
1 parent b6acfcf commit 524143c

File tree

11 files changed

+358
-19
lines changed

11 files changed

+358
-19
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* Show uniq request id in API HTTP response. (#425, @miry)
1010
* Add method to parse `stream.Direction` from string.
1111
Allow to convert `stream.Direction` to string. (#430, @miry)
12+
* Add posibility to write to Output with deadline.
13+
On interrupting badnwidth toxic use non blocking write. (#436, @miry)
1214

1315
# [2.4.0] - 2022-03-07
1416

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
OS := $(shell uname -s)
2+
ARCH := $(shell uname -m)
23
GO_VERSION := $(shell go version | cut -f3 -d" ")
34
GO_MINOR_VERSION := $(shell echo $(GO_VERSION) | cut -f2 -d.)
45
GO_PATCH_VERSION := $(shell echo $(GO_VERSION) | cut -f3 -d. | sed "s/^\s*$$/0/")
@@ -13,8 +14,9 @@ test:
1314
$(MALLOC_ENV) go test -v -race -timeout 1m ./...
1415

1516
.PHONY: test-e2e
16-
test-e2e: build
17+
test-e2e: build container.build
1718
scripts/test-e2e
19+
timeout -v --foreground 20m scripts/test-e2e-hazelcast toxiproxy
1820

1921
.PHONY: test-release
2022
test-release: test bench test-e2e release-dry
@@ -45,6 +47,13 @@ build: dist clean
4547
go build -ldflags="-s -w" -o ./dist/toxiproxy-server ./cmd/server
4648
go build -ldflags="-s -w" -o ./dist/toxiproxy-cli ./cmd/cli
4749

50+
.PHONY: container.build
51+
container.build:
52+
env GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w" -o ./dist/toxiproxy-server-linux-$(ARCH) ./cmd/server
53+
env GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w" -o ./dist/toxiproxy-cli-linux-$(ARCH) ./cmd/cli
54+
docker build -f Dockerfile -t toxiproxy dist
55+
docker run --rm toxiproxy --version
56+
4857
.PHONY: release
4958
release:
5059
goreleaser release --rm-dist

api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func stopBrowsersMiddleware(next http.Handler) http.Handler {
2727
}
2828

2929
func timeoutMiddleware(next http.Handler) http.Handler {
30-
return http.TimeoutHandler(next, 30*time.Second, "")
30+
return http.TimeoutHandler(next, 25*time.Second, "")
3131
}
3232

3333
type ApiServer struct {
@@ -121,7 +121,7 @@ func (server *ApiServer) Listen(host string, port string) {
121121
srv := &http.Server{
122122
Handler: r,
123123
Addr: net.JoinHostPort(host, port),
124-
WriteTimeout: 10 * time.Second,
124+
WriteTimeout: 30 * time.Second,
125125
ReadTimeout: 10 * time.Second,
126126
}
127127

link.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"net"
8+
"time"
89

910
"github.com/rs/zerolog"
1011

@@ -73,7 +74,10 @@ func (link *ToxicLink) Start(
7374
dest io.WriteCloser,
7475
) {
7576
logger := link.Logger
76-
logger.Debug().Msg("Setup connection")
77+
logger.
78+
Debug().
79+
Str("direction", link.Direction()).
80+
Msg("Setup connection")
7781

7882
labels := []string{
7983
link.Direction(),
@@ -133,23 +137,33 @@ func (link *ToxicLink) read(
133137
func (link *ToxicLink) write(
134138
metricLabels []string,
135139
name string,
136-
server *ApiServer,
140+
server *ApiServer, // TODO: Replace with AppConfig for Metrics and Logger
137141
dest io.WriteCloser,
138142
) {
139-
logger := link.Logger
143+
logger := link.Logger.
144+
With().
145+
Str("component", "ToxicLink").
146+
Str("method", "write").
147+
Str("link", name).
148+
Str("proxy", link.proxy.Name).
149+
Str("link_addr", fmt.Sprintf("%p", link)).
150+
Logger()
151+
140152
bytes, err := io.Copy(dest, link.output)
141153
if err != nil {
142154
logger.Warn().
143155
Int64("bytes", bytes).
144156
Err(err).
145-
Msg("Destination terminated")
146-
}
147-
if server.Metrics.proxyMetricsEnabled() {
157+
Msg("Could not write to destination")
158+
} else if server.Metrics.proxyMetricsEnabled() {
148159
server.Metrics.ProxyMetrics.SentBytesTotal.
149160
WithLabelValues(metricLabels...).Add(float64(bytes))
150161
}
162+
151163
dest.Close()
164+
logger.Trace().Msgf("Remove link %s from ToxicCollection", name)
152165
link.toxics.RemoveLink(name)
166+
logger.Trace().Msgf("RemoveConnection %s from Proxy %s", name, link.proxy.Name)
153167
link.proxy.RemoveConnection(name)
154168
}
155169

@@ -211,11 +225,11 @@ func (link *ToxicLink) RemoveToxic(ctx context.Context, toxic *toxics.ToxicWrapp
211225
}
212226
}
213227

214-
log.Trace().Msg("Interrupt the previous toxic to update its output")
228+
log.Trace().Msg("Interrupting the previous toxic to update its output")
215229
stop := make(chan bool)
216-
go func() {
217-
stop <- link.stubs[toxic_index-1].InterruptToxic()
218-
}()
230+
go func(stub *toxics.ToxicStub, stop chan bool) {
231+
stop <- stub.InterruptToxic()
232+
}(link.stubs[toxic_index-1], stop)
219233

220234
// Unblock the previous toxic if it is trying to flush
221235
// If the previous toxic is closed, continue flusing until we reach the end.
@@ -231,9 +245,14 @@ func (link *ToxicLink) RemoveToxic(ctx context.Context, toxic *toxics.ToxicWrapp
231245
if !stopped {
232246
<-stop
233247
}
234-
return
248+
return // TODO: There are some steps after this to clean buffer
249+
}
250+
251+
err := link.stubs[toxic_index].WriteOutput(tmp, 5*time.Second)
252+
if err != nil {
253+
log.Err(err).
254+
Msg("Could not write last packets after interrupt to Output")
235255
}
236-
link.stubs[toxic_index].Output <- tmp
237256
}
238257
}
239258

@@ -244,7 +263,11 @@ func (link *ToxicLink) RemoveToxic(ctx context.Context, toxic *toxics.ToxicWrapp
244263
link.stubs[toxic_index].Close()
245264
return
246265
}
247-
link.stubs[toxic_index].Output <- tmp
266+
err := link.stubs[toxic_index].WriteOutput(tmp, 5*time.Second)
267+
if err != nil {
268+
log.Err(err).
269+
Msg("Could not write last packets after interrupt to Output")
270+
}
248271
}
249272

250273
link.stubs[toxic_index-1].Output = link.stubs[toxic_index].Output

link_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ func TestStateCreated(t *testing.T) {
247247
if flag.Lookup("test.v").DefValue == "true" {
248248
log = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()
249249
}
250+
250251
link := NewToxicLink(nil, collection, stream.Downstream, log)
251252
go link.stubs[0].Run(collection.chain[stream.Downstream][0])
252253
collection.links["test"] = link
@@ -261,3 +262,64 @@ func TestStateCreated(t *testing.T) {
261262
t.Fatalf("New toxic did not have state object created.")
262263
}
263264
}
265+
266+
func TestRemoveToxicWithBrokenConnection(t *testing.T) {
267+
ctx := context.Background()
268+
269+
log := zerolog.Nop()
270+
if flag.Lookup("test.v").DefValue == "true" {
271+
log = zerolog.New(os.Stdout).With().Caller().Timestamp().Logger()
272+
}
273+
ctx = log.WithContext(ctx)
274+
275+
collection := NewToxicCollection(nil)
276+
link := NewToxicLink(nil, collection, stream.Downstream, log)
277+
go link.stubs[0].Run(collection.chain[stream.Downstream][0])
278+
collection.links["test"] = link
279+
280+
toxics := [2]*toxics.ToxicWrapper{
281+
{
282+
Toxic: &toxics.BandwidthToxic{
283+
Rate: 0,
284+
},
285+
Type: "bandwidth",
286+
Direction: stream.Downstream,
287+
Toxicity: 1,
288+
},
289+
{
290+
Toxic: &toxics.BandwidthToxic{
291+
Rate: 0,
292+
},
293+
Type: "bandwidth",
294+
Direction: stream.Upstream,
295+
Toxicity: 1,
296+
},
297+
}
298+
299+
collection.chainAddToxic(toxics[0])
300+
collection.chainAddToxic(toxics[1])
301+
302+
done := make(chan struct{})
303+
defer close(done)
304+
305+
var data uint16 = 42
306+
go func(log zerolog.Logger) {
307+
for {
308+
select {
309+
case <-done:
310+
link.input.Close()
311+
return
312+
case <-time.After(10 * time.Second):
313+
log.Print("Finish load")
314+
return
315+
default:
316+
buf := make([]byte, 2)
317+
binary.BigEndian.PutUint16(buf, data)
318+
link.input.Write(buf)
319+
}
320+
}
321+
}(log)
322+
323+
collection.chainRemoveToxic(ctx, toxics[0])
324+
collection.chainRemoveToxic(ctx, toxics[1])
325+
}

scripts/hazelcast.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://www.hazelcast.com/schema/config
6+
http://www.hazelcast.com/schema/config/hazelcast-config-5.1.xsd">
7+
8+
<properties>
9+
<property name="hazelcast.merge.next.run.delay.seconds">15</property>
10+
<property name="hazelcast.merge.first.run.delay.seconds">20</property>
11+
<property name="hazelcast.partition.migration.chunks.enabled">false</property>
12+
<property name="hazelcast.heartbeat.failuredetector.type">deadline</property>
13+
<property name="hazelcast.heartbeat.interval.seconds">3</property>
14+
<property name="hazelcast.max.no.heartbeat.seconds">10</property>
15+
</properties>
16+
17+
<network>
18+
<public-address>member-proxy:${proxyPort}</public-address>
19+
<port auto-increment="false">5701</port>
20+
<join>
21+
<auto-detection enabled="false"/>
22+
<tcp-ip enabled="true">
23+
<member-list>
24+
<member>member-proxy:${proxyPort0}</member>
25+
<member>member-proxy:${proxyPort1}</member>
26+
<member>member-proxy:${proxyPort2}</member>
27+
</member-list>
28+
</tcp-ip>
29+
</join>
30+
</network>
31+
</hazelcast>

scripts/test-e2e

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ function cleanup() {
2828
}
2929
trap "cleanup" EXIT SIGINT SIGTERM
3030

31+
echo "= Toxiproxy E2E tests"
32+
echo
33+
echo "== Setup"
34+
echo
3135
echo "=== Starting Web service"
3236

3337
pkill -15 "toxiproxy-server" || true
@@ -56,7 +60,6 @@ cli toggle shopify_http
5660
echo -e "-----------------\n"
5761

5862
echo "== Benchmarking"
59-
6063
echo
6164
echo "=== Without toxics"
6265

0 commit comments

Comments
 (0)