Skip to content

Commit ec90997

Browse files
authored
Merge pull request #30 from arduino/remove_channels
Made writes to output stream synchronous
2 parents 5f984bd + 9863c7b commit ec90997

File tree

10 files changed

+693
-135
lines changed

10 files changed

+693
-135
lines changed

.github/workflows/check-taskfiles.yml

+53-14
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/check-taskfiles.md
22
name: Check Taskfiles
33

4-
# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows
4+
env:
5+
# See: https://github.com/actions/setup-node/#readme
6+
NODE_VERSION: 16.x
7+
8+
# See: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
59
on:
10+
create:
611
push:
712
paths:
813
- ".github/workflows/check-taskfiles.ya?ml"
14+
- "package.json"
15+
- "package-lock.json"
916
- "**/Taskfile.ya?ml"
1017
pull_request:
1118
paths:
1219
- ".github/workflows/check-taskfiles.ya?ml"
20+
- "package.json"
21+
- "package-lock.json"
1322
- "**/Taskfile.ya?ml"
1423
schedule:
1524
# Run every Tuesday at 8 AM UTC to catch breakage resulting from changes to the JSON schema.
@@ -18,8 +27,33 @@ on:
1827
repository_dispatch:
1928

2029
jobs:
30+
run-determination:
31+
runs-on: ubuntu-latest
32+
outputs:
33+
result: ${{ steps.determination.outputs.result }}
34+
steps:
35+
- name: Determine if the rest of the workflow should run
36+
id: determination
37+
run: |
38+
RELEASE_BRANCH_REGEX="refs/heads/[0-9]+.[0-9]+.x"
39+
# The `create` event trigger doesn't support `branches` filters, so it's necessary to use Bash instead.
40+
if [[
41+
"${{ github.event_name }}" != "create" ||
42+
"${{ github.ref }}" =~ $RELEASE_BRANCH_REGEX
43+
]]; then
44+
# Run the other jobs.
45+
RESULT="true"
46+
else
47+
# There is no need to run the other jobs.
48+
RESULT="false"
49+
fi
50+
51+
echo "result=$RESULT" >> $GITHUB_OUTPUT
52+
2153
validate:
2254
name: Validate ${{ matrix.file }}
55+
needs: run-determination
56+
if: needs.run-determination.outputs.result == 'true'
2357
runs-on: ubuntu-latest
2458

2559
strategy:
@@ -34,26 +68,31 @@ jobs:
3468
- name: Checkout repository
3569
uses: actions/checkout@v3
3670

71+
- name: Setup Node.js
72+
uses: actions/setup-node@v3
73+
with:
74+
node-version: ${{ env.NODE_VERSION }}
75+
3776
- name: Download JSON schema for Taskfiles
3877
id: download-schema
3978
uses: carlosperate/download-file-action@v2
4079
with:
41-
# See: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/taskfile.json
42-
file-url: https://json.schemastore.org/taskfile.json
80+
# Source: https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/taskfile.json
81+
file-url: https://taskfile.dev/schema.json
4382
location: ${{ runner.temp }}/taskfile-schema
4483

4584
- name: Install JSON schema validator
46-
run: |
47-
sudo npm install \
48-
--global \
49-
ajv-cli \
50-
ajv-formats
85+
run: npm install
86+
5187
- name: Validate ${{ matrix.file }}
5288
run: |
5389
# See: https://github.com/ajv-validator/ajv-cli#readme
54-
ajv validate \
55-
--all-errors \
56-
--strict=false \
57-
-c ajv-formats \
58-
-s "${{ steps.download-schema.outputs.file-path }}" \
59-
-d "${{ matrix.file }}"
90+
npx \
91+
--package=ajv-cli \
92+
--package=ajv-formats \
93+
ajv validate \
94+
--all-errors \
95+
--strict=false \
96+
-c ajv-formats \
97+
-s "${{ steps.download-schema.outputs.file-path }}" \
98+
-d "${{ matrix.file }}"

.github/workflows/test-go-task.yml

+1
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,5 @@ jobs:
9898
with:
9999
file: ${{ matrix.module.path }}coverage_unit.txt
100100
flags: ${{ matrix.module.codecov-flags }}
101+
token: ${{ secrets.CODECOV_TOKEN }}
101102
fail_ci_if_error: ${{ github.repository == 'arduino/pluggable-discovery-protocol-handler' }}

.licenses/pluggable-discovery-protocol-handler/go/github.com/arduino/go-paths-helper.dep.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
---
22
name: github.com/arduino/go-paths-helper
3-
version: v1.6.1
3+
version: v1.8.0
44
type: go
5-
summary:
5+
summary:
66
homepage: https://pkg.go.dev/github.com/arduino/go-paths-helper
77
license: gpl-2.0-or-later
88
licenses:

.licenses/pluggable-discovery-protocol-handler/go/github.com/arduino/go-properties-orderedmap.dep.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: github.com/arduino/go-properties-orderedmap
3-
version: v1.6.0
3+
version: v1.7.1
44
type: go
55
summary: Package properties is a library for handling maps of hierarchical properties.
66
homepage: https://pkg.go.dev/github.com/arduino/go-properties-orderedmap

discovery.go

+49-64
Original file line numberDiff line numberDiff line change
@@ -86,24 +86,23 @@ type ErrorCallback func(err string)
8686
// it must be created using the NewServer function.
8787
type Server struct {
8888
impl Discovery
89-
outputChan chan *message
90-
outputWaiter sync.WaitGroup
9189
userAgent string
9290
reqProtocolVersion int
9391
initialized bool
9492
started bool
9593
syncStarted bool
9694
cachedPorts map[string]*Port
9795
cachedErr string
96+
output io.Writer
97+
outputMutex sync.Mutex
9898
}
9999

100100
// NewServer creates a new discovery server backed by the
101101
// provided pluggable discovery implementation. To start the server
102102
// use the Run method.
103103
func NewServer(impl Discovery) *Server {
104104
return &Server{
105-
impl: impl,
106-
outputChan: make(chan *message),
105+
impl: impl,
107106
}
108107
}
109108

@@ -113,20 +112,20 @@ func NewServer(impl Discovery) *Server {
113112
// the input stream is closed. In case of IO error the error is
114113
// returned.
115114
func (d *Server) Run(in io.Reader, out io.Writer) error {
116-
d.startOutputProcessor(out)
115+
d.output = out
117116
reader := bufio.NewReader(in)
118117
for {
119118
fullCmd, err := reader.ReadString('\n')
120119
if err != nil {
121-
d.outputChan <- messageError("command_error", err.Error())
120+
d.send(messageError("command_error", err.Error()))
122121
return err
123122
}
124123
fullCmd = strings.TrimSpace(fullCmd)
125124
split := strings.Split(fullCmd, " ")
126125
cmd := strings.ToUpper(split[0])
127126

128127
if !d.initialized && cmd != "HELLO" && cmd != "QUIT" {
129-
d.outputChan <- messageError("command_error", fmt.Sprintf("First command must be HELLO, but got '%s'", cmd))
128+
d.send(messageError("command_error", fmt.Sprintf("First command must be HELLO, but got '%s'", cmd)))
130129
continue
131130
}
132131

@@ -142,61 +141,62 @@ func (d *Server) Run(in io.Reader, out io.Writer) error {
142141
case "STOP":
143142
d.stop()
144143
case "QUIT":
145-
d.quit()
144+
d.impl.Quit()
145+
d.send(messageOk("quit"))
146146
return nil
147147
default:
148-
d.outputChan <- messageError("command_error", fmt.Sprintf("Command %s not supported", cmd))
148+
d.send(messageError("command_error", fmt.Sprintf("Command %s not supported", cmd)))
149149
}
150150
}
151151
}
152152

153153
func (d *Server) hello(cmd string) {
154154
if d.initialized {
155-
d.outputChan <- messageError("hello", "HELLO already called")
155+
d.send(messageError("hello", "HELLO already called"))
156156
return
157157
}
158158
re := regexp.MustCompile(`^(\d+) "([^"]+)"$`)
159159
matches := re.FindStringSubmatch(cmd)
160160
if len(matches) != 3 {
161-
d.outputChan <- messageError("hello", "Invalid HELLO command")
161+
d.send(messageError("hello", "Invalid HELLO command"))
162162
return
163163
}
164164
d.userAgent = matches[2]
165165
v, err := strconv.ParseInt(matches[1], 10, 64)
166166
if err != nil {
167-
d.outputChan <- messageError("hello", "Invalid protocol version: "+matches[2])
167+
d.send(messageError("hello", "Invalid protocol version: "+matches[2]))
168168
return
169169
}
170170
d.reqProtocolVersion = int(v)
171171
if err := d.impl.Hello(d.userAgent, 1); err != nil {
172-
d.outputChan <- messageError("hello", err.Error())
172+
d.send(messageError("hello", err.Error()))
173173
return
174174
}
175-
d.outputChan <- &message{
175+
d.send(&message{
176176
EventType: "hello",
177177
ProtocolVersion: 1, // Protocol version 1 is the only supported for now...
178178
Message: "OK",
179-
}
179+
})
180180
d.initialized = true
181181
}
182182

183183
func (d *Server) start() {
184184
if d.started {
185-
d.outputChan <- messageError("start", "Discovery already STARTed")
185+
d.send(messageError("start", "Discovery already STARTed"))
186186
return
187187
}
188188
if d.syncStarted {
189-
d.outputChan <- messageError("start", "Discovery already START_SYNCed, cannot START")
189+
d.send(messageError("start", "Discovery already START_SYNCed, cannot START"))
190190
return
191191
}
192192
d.cachedPorts = map[string]*Port{}
193193
d.cachedErr = ""
194194
if err := d.impl.StartSync(d.eventCallback, d.errorCallback); err != nil {
195-
d.outputChan <- messageError("start", "Cannot START: "+err.Error())
195+
d.send(messageError("start", "Cannot START: "+err.Error()))
196196
return
197197
}
198198
d.started = true
199-
d.outputChan <- messageOk("start")
199+
d.send(messageOk("start"))
200200
}
201201

202202
func (d *Server) eventCallback(event string, port *Port) {
@@ -215,99 +215,84 @@ func (d *Server) errorCallback(msg string) {
215215

216216
func (d *Server) list() {
217217
if !d.started {
218-
d.outputChan <- messageError("list", "Discovery not STARTed")
218+
d.send(messageError("list", "Discovery not STARTed"))
219219
return
220220
}
221221
if d.syncStarted {
222-
d.outputChan <- messageError("list", "discovery already START_SYNCed, LIST not allowed")
222+
d.send(messageError("list", "discovery already START_SYNCed, LIST not allowed"))
223223
return
224224
}
225225
if d.cachedErr != "" {
226-
d.outputChan <- messageError("list", d.cachedErr)
226+
d.send(messageError("list", d.cachedErr))
227227
return
228228
}
229229
ports := []*Port{}
230230
for _, port := range d.cachedPorts {
231231
ports = append(ports, port)
232232
}
233-
d.outputChan <- &message{
233+
d.send(&message{
234234
EventType: "list",
235235
Ports: &ports,
236-
}
236+
})
237237
}
238238

239239
func (d *Server) startSync() {
240240
if d.syncStarted {
241-
d.outputChan <- messageError("start_sync", "Discovery already START_SYNCed")
241+
d.send(messageError("start_sync", "Discovery already START_SYNCed"))
242242
return
243243
}
244244
if d.started {
245-
d.outputChan <- messageError("start_sync", "Discovery already STARTed, cannot START_SYNC")
245+
d.send(messageError("start_sync", "Discovery already STARTed, cannot START_SYNC"))
246246
return
247247
}
248248
if err := d.impl.StartSync(d.syncEvent, d.errorEvent); err != nil {
249-
d.outputChan <- messageError("start_sync", "Cannot START_SYNC: "+err.Error())
249+
d.send(messageError("start_sync", "Cannot START_SYNC: "+err.Error()))
250250
return
251251
}
252252
d.syncStarted = true
253-
d.outputChan <- messageOk("start_sync")
253+
d.send(messageOk("start_sync"))
254254
}
255255

256256
func (d *Server) stop() {
257257
if !d.syncStarted && !d.started {
258-
d.outputChan <- messageError("stop", "Discovery already STOPped")
258+
d.send(messageError("stop", "Discovery already STOPped"))
259259
return
260260
}
261261
if err := d.impl.Stop(); err != nil {
262-
d.outputChan <- messageError("stop", "Cannot STOP: "+err.Error())
262+
d.send(messageError("stop", "Cannot STOP: "+err.Error()))
263263
return
264264
}
265265
d.started = false
266266
if d.syncStarted {
267267
d.syncStarted = false
268268
}
269-
d.outputChan <- messageOk("stop")
269+
d.send(messageOk("stop"))
270270
}
271271

272272
func (d *Server) syncEvent(event string, port *Port) {
273-
d.outputChan <- &message{
273+
d.send(&message{
274274
EventType: event,
275275
Port: port,
276-
}
277-
}
278-
279-
func (d *Server) quit() {
280-
d.impl.Quit()
281-
d.outputChan <- messageOk("quit")
282-
close(d.outputChan)
283-
// If we don't wait for all messages
284-
// to be consumed by the output processor
285-
// we risk not printing the "quit" message.
286-
// This may cause issues to consumers of
287-
// the discovery since they expect a message
288-
// that is never sent.
289-
d.outputWaiter.Wait()
276+
})
290277
}
291278

292279
func (d *Server) errorEvent(msg string) {
293-
d.outputChan <- messageError("start_sync", msg)
280+
d.send(messageError("start_sync", msg))
294281
}
295282

296-
func (d *Server) startOutputProcessor(outWriter io.Writer) {
297-
// Start go routine to serialize messages printing
298-
d.outputWaiter.Add(1)
299-
go func() {
300-
for msg := range d.outputChan {
301-
data, err := json.MarshalIndent(msg, "", " ")
302-
if err != nil {
303-
// We are certain that this will be marshalled correctly
304-
// so we don't handle the error
305-
data, _ = json.MarshalIndent(messageError("command_error", err.Error()), "", " ")
306-
}
307-
fmt.Fprintln(outWriter, string(data))
308-
}
309-
// We finished consuming all messages, now
310-
// we can exit for real
311-
d.outputWaiter.Done()
312-
}()
283+
func (d *Server) send(msg *message) {
284+
data, err := json.MarshalIndent(msg, "", " ")
285+
if err != nil {
286+
// We are certain that this will be marshalled correctly
287+
// so we don't handle the error
288+
data, _ = json.MarshalIndent(messageError("command_error", err.Error()), "", " ")
289+
}
290+
data = append(data, '\n')
291+
292+
d.outputMutex.Lock()
293+
defer d.outputMutex.Unlock()
294+
n, err := d.output.Write(data)
295+
if n != len(data) || err != nil {
296+
panic("ERROR")
297+
}
313298
}

0 commit comments

Comments
 (0)