Skip to content

Commit 93291ef

Browse files
refactor(runtime): migrate runtime codes from cli project to yomo (#189)
* refactor(runtime): migrate `runtime` related codes from cli project to yomo * refactor(conn): add a common quic conn for client and server * fix(mesh): retry to connect downstream zipper when the conn is disconnected
1 parent 2588417 commit 93291ef

15 files changed

Lines changed: 973 additions & 217 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ require (
99
github.com/stretchr/objx v0.1.1 // indirect
1010
github.com/yomorun/y3-codec-golang v1.6.9
1111
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f // indirect
12-
gopkg.in/yaml.v2 v2.4.0 // indirect
12+
gopkg.in/yaml.v2 v2.4.0
1313
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
1414
)

pkg/client/client.go

Lines changed: 57 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,6 @@ import (
1212
"time"
1313

1414
"github.com/yomorun/yomo/pkg/quic"
15-
"github.com/yomorun/yomo/pkg/rx"
16-
)
17-
18-
const (
19-
// ClientTypeSource represents the client type of Source.
20-
ClientTypeSource = "source"
21-
22-
// ClientTypeServerless represents the client type of Serverless.
23-
ClientTypeServerless = "serverless"
24-
25-
// ClientTypeZipperSender represents the client type of ZipperSender.
26-
ClientTypeZipperSender = "zipper-sender"
27-
)
28-
29-
var (
30-
// SignalHeartbeat represents the signal of Heartbeat.
31-
SignalHeartbeat = []byte{0}
32-
33-
// SignalAccepted represents the signal of Accpeted.
34-
SignalAccepted = []byte{1}
35-
36-
// SignalFlowSink represents the signal for flow/sink.
37-
SignalFlowSink = []byte{0, 0}
3815
)
3916

4017
// NegotiationPayload represents the payload for negotiation.
@@ -56,69 +33,44 @@ type client interface {
5633
RetryWithCount(count int) bool
5734
}
5835

59-
// SourceClient is the client for YoMo-Source.
60-
// https://yomo.run/source
61-
type SourceClient interface {
62-
client
63-
64-
// Connect to YoMo-Zipper
65-
Connect(ip string, port int) (SourceClient, error)
66-
}
67-
68-
// ServerlessClient is the client for YoMo-Serverless.
69-
type ServerlessClient interface {
70-
client
71-
72-
// Connect to YoMo-Zipper
73-
Connect(ip string, port int) (ServerlessClient, error)
74-
75-
// Pipe the Handler function.
76-
Pipe(f func(rxstream rx.RxStream) rx.RxStream)
77-
}
78-
79-
// ZipperSenderClient is the client for Zipper-Sender to connect the downsteam Zipper-Receiver in edge-mesh.
80-
type ZipperSenderClient interface {
81-
client
82-
83-
// Connect to downsteam Zipper-Receiver
84-
Connect(ip string, port int) (ZipperSenderClient, error)
85-
}
86-
8736
type clientImpl struct {
37+
conn *quic.QuicConn
8838
zipperIP string
8939
zipperPort int
90-
name string
91-
clientType string
9240
readers chan io.Reader
9341
writer io.Writer
9442
session quic.Client
95-
signal quic.Stream
96-
stream quic.Stream
97-
heartbeat chan byte
9843
once *sync.Once
9944
}
10045

101-
type sourceClientImpl struct {
102-
*clientImpl
103-
}
104-
105-
type serverlessClientImpl struct {
106-
*clientImpl
107-
}
108-
109-
type zipperSenderClientImpl struct {
110-
*clientImpl
111-
}
112-
11346
// newClient creates a new client.
11447
func newClient(appName string, clientType string) *clientImpl {
11548
c := &clientImpl{
116-
name: appName,
117-
clientType: clientType,
118-
readers: make(chan io.Reader, 1),
119-
heartbeat: make(chan byte),
120-
once: new(sync.Once),
49+
conn: quic.NewQuicConn(appName, clientType),
50+
readers: make(chan io.Reader, 1),
51+
once: new(sync.Once),
52+
}
53+
54+
c.conn.OnHeartbeatReceived = func() {
55+
// when the client received the heartbeat from server, send it back back to server (ping/pong).
56+
c.conn.SendSignal(quic.SignalHeartbeat)
12157
}
58+
59+
c.conn.OnHeartbeatExpired = func() {
60+
c.once.Do(func() {
61+
// reset stream to nil.
62+
c.conn.Stream = nil
63+
64+
// reconnect when the heartbeat is expired.
65+
c.connect(c.zipperIP, c.zipperPort)
66+
67+
// reset the sync.Once after 5s.
68+
time.AfterFunc(5*time.Second, func() {
69+
c.once = new(sync.Once)
70+
})
71+
})
72+
}
73+
12274
return c
12375
}
12476

@@ -129,65 +81,78 @@ func (c *clientImpl) connect(ip string, port int) (*clientImpl, error) {
12981
c.zipperPort = port
13082
addr := fmt.Sprintf("%s:%d", ip, port)
13183
log.Println("Connecting to zipper", addr, "...")
84+
13285
// connect to yomo-zipper
13386
quic_cli, err := quic.NewClient(addr)
13487
if err != nil {
13588
fmt.Println("client [NewClient] Error:", err)
13689
return c, err
13790
}
91+
13892
// create stream
13993
quic_stream, err := quic_cli.CreateStream(context.Background())
14094
if err != nil {
14195
fmt.Println("client [CreateStream] Error:", err)
14296
return c, err
14397
}
14498

99+
// set session and signal
145100
c.session = quic_cli
146-
c.signal = quic_stream
101+
c.conn.Signal = quic_stream
147102

148103
// send negotiation payload to zipper
149104
payload := NegotiationPayload{
150-
AppName: c.name,
151-
ClientType: c.clientType,
105+
AppName: c.conn.Name,
106+
ClientType: c.conn.Type,
152107
}
153108
buf, _ := json.Marshal(payload)
154-
_, err = c.signal.Write(buf)
109+
err = c.conn.SendSignal(buf)
155110

156111
if err != nil {
157112
fmt.Println("client [Write] Error:", err)
158113
return c, err
159114
}
160115

161-
// flow, sink create stream or heartbeat
162116
accepted := make(chan bool)
163117

118+
c.handleSignal(accepted)
119+
c.conn.Healthcheck()
120+
121+
// waiting when the connection is accepted.
122+
<-accepted
123+
log.Print("✅ Connected to zipper ", addr)
124+
return c, nil
125+
}
126+
127+
// handleSignal handles the logic when receiving signal from server.
128+
func (c *clientImpl) handleSignal(accepted chan bool) {
164129
go func() {
165130
defer close(accepted)
166131
for {
167132
buf := make([]byte, 2)
168-
n, err := c.signal.Read(buf)
133+
n, err := c.conn.Signal.Read(buf)
169134
if err != nil {
170135
break
171136
}
172137
value := buf[:n]
173138

174-
if bytes.Equal(value, SignalHeartbeat) {
139+
if bytes.Equal(value, quic.SignalHeartbeat) {
175140
// heartbeart
176-
c.heartbeat <- buf[0]
177-
} else if bytes.Equal(value, SignalAccepted) {
141+
c.conn.Heartbeat <- buf[0]
142+
} else if bytes.Equal(value, quic.SignalAccepted) {
178143
// accepted
179-
if c.clientType == ClientTypeSource || c.clientType == ClientTypeZipperSender {
180-
// create the stream from source.
144+
if c.conn.Type == quic.ConnTypeSource || c.conn.Type == quic.ConnTypeZipperSender {
145+
// create stream for source.
181146
stream, err := c.session.CreateStream(context.Background())
182147
if err != nil {
183148
fmt.Println("client [session.CreateStream] Error:", err)
184149
break
185150
}
186-
c.stream = stream
151+
c.conn.Stream = stream
187152
}
188153
accepted <- true
189-
} else if bytes.Equal(value, SignalFlowSink) {
190-
// create stream
154+
} else if bytes.Equal(value, quic.SignalFlowSink) {
155+
// create stream for flow/sink.
191156
stream, err := c.session.CreateStream(context.Background())
192157

193158
if err != nil {
@@ -197,49 +162,16 @@ func (c *clientImpl) connect(ip string, port int) (*clientImpl, error) {
197162

198163
c.readers <- stream
199164
c.writer = stream
200-
stream.Write(SignalHeartbeat)
201-
}
202-
}
203-
}()
204-
205-
go func() {
206-
defer c.Close()
207-
for {
208-
select {
209-
case item, ok := <-c.heartbeat:
210-
if !ok {
211-
return
212-
}
213-
_, err := c.signal.Write([]byte{item})
214-
215-
if err != nil {
216-
return
217-
}
218-
case <-time.After(5 * time.Second):
219-
// reconnect if didn't receive the heartbeat after 5s.
220-
c.once.Do(func() {
221-
c.connect(c.zipperIP, c.zipperPort)
222-
223-
// reset the sync.Once after 5s.
224-
time.AfterFunc(5*time.Second, func() {
225-
c.once = new(sync.Once)
226-
})
227-
})
165+
stream.Write(quic.SignalHeartbeat)
228166
}
229167
}
230-
231168
}()
232-
233-
// waiting when the connection is accepted.
234-
<-accepted
235-
log.Print("✅ Connected to zipper ", addr)
236-
return c, nil
237169
}
238170

239171
// Write the data to downstream.
240172
func (c *clientImpl) Write(b []byte) (int, error) {
241-
if c.stream != nil {
242-
return c.stream.Write(b)
173+
if c.conn.Stream != nil {
174+
return c.conn.Stream.Write(b)
243175
} else {
244176
return 0, errors.New("not found stream")
245177
}
@@ -273,85 +205,7 @@ func (c *clientImpl) RetryWithCount(count int) bool {
273205
// Close the client.
274206
func (c *clientImpl) Close() error {
275207
err := c.session.Close()
276-
c.heartbeat = make(chan byte)
277-
c.signal = nil
208+
c.conn.Heartbeat = make(chan byte)
209+
c.conn.Signal = nil
278210
return err
279211
}
280-
281-
// NewSource setups the client of YoMo-Source.
282-
func NewSource(appName string) SourceClient {
283-
c := &sourceClientImpl{
284-
clientImpl: newClient(appName, ClientTypeSource),
285-
}
286-
return c
287-
}
288-
289-
// Connect to yomo-zipper.
290-
func (c *sourceClientImpl) Connect(ip string, port int) (SourceClient, error) {
291-
cli, err := c.connect(ip, port)
292-
return &sourceClientImpl{
293-
cli,
294-
}, err
295-
}
296-
297-
// NewServerless setups the client of YoMo-Serverless.
298-
// The "appName" should match the name of flows (or sinks) in workflow.yaml in zipper.
299-
func NewServerless(appName string) ServerlessClient {
300-
c := &serverlessClientImpl{
301-
clientImpl: newClient(appName, ClientTypeServerless),
302-
}
303-
return c
304-
}
305-
306-
// Connect to yomo-zipper.
307-
func (c *serverlessClientImpl) Connect(ip string, port int) (ServerlessClient, error) {
308-
cli, err := c.connect(ip, port)
309-
return &serverlessClientImpl{
310-
cli,
311-
}, err
312-
}
313-
314-
// Pipe the handler function in flow/sink serverless.
315-
func (c *serverlessClientImpl) Pipe(f func(rxstream rx.RxStream) rx.RxStream) {
316-
rxstream := rx.FromReaderWithDecoder(c.readers)
317-
stream := f(rxstream)
318-
319-
rxstream.Connect(context.Background())
320-
321-
for customer := range stream.Observe() {
322-
if customer.Error() {
323-
panic(customer.E)
324-
} else if customer.V != nil {
325-
if c.writer == nil {
326-
continue
327-
}
328-
329-
buf, ok := (customer.V).([]byte)
330-
if !ok {
331-
log.Print("❌ Please add the encode/marshal operator in the end of your Serverless handler.")
332-
continue
333-
}
334-
_, err := c.writer.Write(buf)
335-
if err != nil {
336-
log.Print("❌ Send data to zipper failed. ", err)
337-
}
338-
}
339-
340-
}
341-
}
342-
343-
// NewZipperSender setups the client of Zipper-Sender.
344-
func NewZipperSender(appName string) ZipperSenderClient {
345-
c := &zipperSenderClientImpl{
346-
clientImpl: newClient(appName, ClientTypeZipperSender),
347-
}
348-
return c
349-
}
350-
351-
// Connect to downstream zipper-receiver in edge-mesh.
352-
func (c *zipperSenderClientImpl) Connect(ip string, port int) (ZipperSenderClient, error) {
353-
cli, err := c.connect(ip, port)
354-
return &zipperSenderClientImpl{
355-
cli,
356-
}, err
357-
}

0 commit comments

Comments
 (0)