Skip to content
This repository was archived by the owner on Jan 24, 2023. It is now read-only.

Commit ef529c6

Browse files
committed
refactor call locking; add uuid to call tracking
We now only lock the specific call entry during Submit(), reducing contention on global call map. Additionally, we generate a UUID for each handled call to ensure uniquenes on Zammad's side.
1 parent 495fe99 commit ef529c6

File tree

3 files changed

+53
-29
lines changed

3 files changed

+53
-29
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/regiohelden/innovazammad
33
require (
44
github.com/cenkalti/backoff v2.1.1+incompatible
55
github.com/fiorix/wsdl2go v1.4.7-0.20190109133134-7389f5f15a72
6+
github.com/google/uuid v1.1.0
67
github.com/kr/pretty v0.1.0 // indirect
78
github.com/pkg/errors v0.8.1
89
github.com/sirupsen/logrus v1.3.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
77
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
88
github.com/fiorix/wsdl2go v1.4.7-0.20190109133134-7389f5f15a72 h1:386HHpLfZsm8FnY1VvYgapUGnHYySkTfRh5NHhLGxCY=
99
github.com/fiorix/wsdl2go v1.4.7-0.20190109133134-7389f5f15a72/go.mod h1:5aVoGQHyBMuQfxSRfTa6rqAl/2yQK/Sma6vRvLLYAfI=
10+
github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s=
11+
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1012
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
1113
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
1214
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=

zammad/zammad.go

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66
"fmt"
77
"net/http"
88
"net/url"
9-
"strconv"
109
"strings"
1110
"sync"
1211

12+
"github.com/google/uuid"
1313
"github.com/pkg/errors"
1414
"github.com/regiohelden/innovazammad/config"
1515
"github.com/regiohelden/innovazammad/innovaphone"
@@ -47,10 +47,16 @@ func (s State) MarshalJSON() ([]byte, error) {
4747
return []byte(fmt.Sprintf("\"%s\"", s.String())), nil
4848
}
4949

50-
type callStateMap map[int]State
50+
type callEntry struct {
51+
State State
52+
UUID uuid.UUID
53+
sync.Mutex
54+
}
55+
56+
type callMap map[int]*callEntry
5157

5258
// String is used by expvar
53-
func (cs callStateMap) String() string {
59+
func (cs callMap) String() string {
5460
out, err := json.Marshal(cs)
5561
if err != nil {
5662
return err.Error()
@@ -60,7 +66,7 @@ func (cs callStateMap) String() string {
6066

6167
// Session keeps track of the states successfully submitted to Zammad
6268
type Session struct {
63-
callStateMap callStateMap
69+
callMap callMap
6470
sync.Mutex
6571
}
6672

@@ -73,52 +79,67 @@ type stateTransition struct {
7379
// The result should be used as a singleton.
7480
func NewSession() *Session {
7581
calls := &Session{
76-
callStateMap: callStateMap{},
82+
callMap: callMap{},
7783
}
78-
expvar.Publish("Session", calls.callStateMap)
84+
expvar.Publish("Session", calls.callMap)
7985
return calls
8086
}
8187

8288
// Get returns a State for the given callID. If no state is known, will return StateNew.
83-
func (zs *Session) get(callID int) State {
84-
if _, ok := zs.callStateMap[callID]; !ok {
85-
return StateNew
89+
func (zs *Session) get(callID int) (*callEntry, error) {
90+
zs.Lock()
91+
defer zs.Unlock()
92+
if _, ok := zs.callMap[callID]; !ok {
93+
UUID, err := uuid.NewRandom()
94+
if err != nil {
95+
return nil, err
96+
}
97+
return &callEntry{
98+
State: StateNew,
99+
UUID: UUID,
100+
}, nil
86101
}
87-
return zs.callStateMap[callID]
102+
return zs.callMap[callID], nil
88103
}
89104

90-
func (zs *Session) update(callID int, state State) {
91-
if state == StateDisconnected {
92-
delete(zs.callStateMap, callID)
105+
func (zs *Session) setOrUpdate(callID int, newEntry *callEntry) {
106+
if newEntry.State == StateDisconnected {
107+
zs.Lock()
108+
defer zs.Unlock()
109+
delete(zs.callMap, callID)
110+
} else if curEntry, ok := zs.callMap[callID]; ok {
111+
curEntry.State = newEntry.State
93112
} else {
94-
zs.callStateMap[callID] = state
113+
zs.callMap[callID] = newEntry
95114
}
96115
}
97116

98117
// Submit sends a call to Zammad, if we are aware of it and can correctly map its state to some known Zammad state
99118
func (zs *Session) Submit(call *innovaphone.CallInSession) error {
100-
zs.Lock()
101-
defer zs.Unlock()
102-
103119
src, dst, err := call.GetSourceAndDestination()
104120
if err != nil {
105121
return err
106122
}
107123
dir := call.GetDirection()
108124
newState := call.GetState()
109125

110-
curState := zs.get(call.Call)
111-
var setState State // used to only update our state if the call was submitted
126+
entry, err := zs.get(call.Call)
127+
if err != nil {
128+
return err
129+
}
130+
// ensure we serialize access to one specific call
131+
entry.Lock()
132+
defer entry.Unlock()
112133

113134
// only skip new calls, because a call-forwarding may send the call outside of the filtered group, meaning we have to
114135
// handle calls we would otherwise filter out based on involved numbers
115-
if curState == StateNew && !call.ShouldHandle() {
136+
if entry.State == StateNew && !call.ShouldHandle() {
116137
logrus.WithField("call", call).Debugf("skipping call for number not in group '%s'", config.Global.PBX.FilterOnGroup)
117138
return nil
118139
}
119140

120141
content := url.Values{
121-
"callId": []string{strconv.Itoa(call.Call)},
142+
"callId": []string{entry.UUID.String()},
122143
"from": []string{normalizeNumber(src.E164)},
123144
"to": []string{normalizeNumber(dst.E164)},
124145
"direction": []string{dir.String()},
@@ -132,38 +153,38 @@ func (zs *Session) Submit(call *innovaphone.CallInSession) error {
132153
user = src.Cn
133154
}
134155

135-
transition := stateTransition{curState: curState, newState: newState}
156+
transition := stateTransition{curState: entry.State, newState: newState}
136157
switch transition {
137158
case
138159
stateTransition{StateNew, innovaphone.StateCallProc},
139160
stateTransition{StateNew, innovaphone.StateAlert}:
140161
// we do not get StateAlert on outgoing calls, so we have to assume StateCallProc is enough
141162
content.Set("event", "newCall")
142163
content.Set("user", user)
143-
setState = StateRinging
164+
entry.State = StateRinging
144165
case stateTransition{StateRinging, innovaphone.StateConnect}:
145166
content.Set("event", "answer")
146167
if dir == innovaphone.DirectionInbound {
147168
content.Set("user", user)
148169
content.Set("answeringNumber", normalizeNumber(dst.E164))
149170
}
150-
setState = StateConnected
171+
entry.State = StateConnected
151172
case stateTransition{StateRinging, innovaphone.StateDisconnectSent}:
152173
content.Set("event", "hangup")
153174
content.Set("cause", "cancel")
154-
setState = StateDisconnected
175+
entry.State = StateDisconnected
155176
case stateTransition{StateRinging, innovaphone.StateDisconnectReceived}:
156177
content.Set("event", "hangup")
157178
content.Set("cause", "noAnswer")
158-
setState = StateDisconnected
179+
entry.State = StateDisconnected
159180
case
160181
stateTransition{StateConnected, innovaphone.StateDisconnectSent},
161182
stateTransition{StateConnected, innovaphone.StateDisconnectReceived}:
162183
content.Set("event", "hangup")
163184
content.Set("cause", "normalClearing")
164-
setState = StateDisconnected
185+
entry.State = StateDisconnected
165186
default:
166-
logrus.WithField("call", call).Debugf("ignoring unknown state transition %s → %s", curState, newState)
187+
logrus.WithField("call", call).Debugf("ignoring unknown state transition %s → %s", transition.curState, transition.newState)
167188
return nil
168189
}
169190

@@ -180,7 +201,7 @@ func (zs *Session) Submit(call *innovaphone.CallInSession) error {
180201
if resp.StatusCode < 200 || resp.StatusCode > 299 {
181202
return fmt.Errorf("could not submit to Zammad: %s", resp.Status)
182203
}
183-
zs.update(call.Call, setState)
204+
zs.setOrUpdate(call.Call, entry)
184205
return nil
185206
}
186207

0 commit comments

Comments
 (0)