Skip to content

Commit a386713

Browse files
authored
feat: add in-memory lock (#399)
1 parent 9a7dbef commit a386713

File tree

7 files changed

+444
-0
lines changed

7 files changed

+444
-0
lines changed

cmd/layotto/main.go

+4
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ import (
108108
"mosn.io/layotto/components/lock"
109109
lock_consul "mosn.io/layotto/components/lock/consul"
110110
lock_etcd "mosn.io/layotto/components/lock/etcd"
111+
lock_inmemory "mosn.io/layotto/components/lock/in-memory"
111112
lock_mongo "mosn.io/layotto/components/lock/mongo"
112113
lock_redis "mosn.io/layotto/components/lock/redis"
113114
lock_zookeeper "mosn.io/layotto/components/lock/zookeeper"
@@ -353,6 +354,9 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp
353354
runtime_lock.NewFactory("mongo", func() lock.LockStore {
354355
return lock_mongo.NewMongoLock(log.DefaultLogger)
355356
}),
357+
runtime_lock.NewFactory("in-memory", func() lock.LockStore {
358+
return lock_inmemory.NewInMemoryLock()
359+
}),
356360
),
357361

358362
// bindings

cmd/layotto_multiple_api/main.go

+4
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ import (
102102
"mosn.io/layotto/components/lock"
103103
lock_consul "mosn.io/layotto/components/lock/consul"
104104
lock_etcd "mosn.io/layotto/components/lock/etcd"
105+
lock_inmemory "mosn.io/layotto/components/lock/in-memory"
105106
lock_redis "mosn.io/layotto/components/lock/redis"
106107
lock_zookeeper "mosn.io/layotto/components/lock/zookeeper"
107108
runtime_lock "mosn.io/layotto/pkg/runtime/lock"
@@ -347,6 +348,9 @@ func NewRuntimeGrpcServer(data json.RawMessage, opts ...grpc.ServerOption) (mgrp
347348
runtime_lock.NewFactory("consul", func() lock.LockStore {
348349
return lock_consul.NewConsulLock(log.DefaultLogger)
349350
}),
351+
runtime_lock.NewFactory("in-memory", func() lock.LockStore {
352+
return lock_inmemory.NewInMemoryLock()
353+
}),
350354
),
351355

352356
// bindings
+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright 2021 Layotto Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package in_memory
18+
19+
import (
20+
"mosn.io/layotto/components/lock"
21+
"sync"
22+
"time"
23+
)
24+
25+
type InMemoryLock struct {
26+
features []lock.Feature
27+
data *lockMap
28+
}
29+
30+
// memoryLock is a lock holder
31+
type memoryLock struct {
32+
key string
33+
owner string
34+
expireTime time.Time
35+
lock int
36+
}
37+
38+
type lockMap struct {
39+
sync.Mutex
40+
locks map[string]*memoryLock
41+
}
42+
43+
func NewInMemoryLock() *InMemoryLock {
44+
return &InMemoryLock{
45+
features: make([]lock.Feature, 0),
46+
data: &lockMap{
47+
locks: make(map[string]*memoryLock),
48+
},
49+
}
50+
}
51+
52+
func (s *InMemoryLock) Init(_ lock.Metadata) error {
53+
return nil
54+
}
55+
56+
func (s *InMemoryLock) Features() []lock.Feature {
57+
return s.features
58+
}
59+
60+
// Try to add a lock. Currently this is a non-reentrant lock
61+
func (s *InMemoryLock) TryLock(req *lock.TryLockRequest) (*lock.TryLockResponse, error) {
62+
s.data.Lock()
63+
defer s.data.Unlock()
64+
// 1. Find the memoryLock for this resourceId
65+
item, ok := s.data.locks[req.ResourceId]
66+
if !ok {
67+
item = &memoryLock{
68+
key: req.ResourceId,
69+
//0 unlock, 1 lock
70+
lock: 0,
71+
}
72+
s.data.locks[req.ResourceId] = item
73+
}
74+
75+
// 2. Construct a new one if the lockData has expired
76+
//check expire
77+
if item.owner != "" && time.Now().After(item.expireTime) {
78+
item = &memoryLock{
79+
key: req.ResourceId,
80+
lock: 0,
81+
}
82+
s.data.locks[req.ResourceId] = item
83+
}
84+
85+
// 3. Check if it has been locked by others.
86+
// Currently this is a non-reentrant lock
87+
if item.lock == 1 {
88+
//lock failed
89+
return &lock.TryLockResponse{
90+
Success: false,
91+
}, nil
92+
}
93+
94+
// 4. Update owner information
95+
item.lock = 1
96+
item.owner = req.LockOwner
97+
item.expireTime = time.Now().Add(time.Second * time.Duration(req.Expire))
98+
99+
return &lock.TryLockResponse{
100+
Success: true,
101+
}, nil
102+
}
103+
104+
func (s *InMemoryLock) Unlock(req *lock.UnlockRequest) (*lock.UnlockResponse, error) {
105+
s.data.Lock()
106+
defer s.data.Unlock()
107+
// 1. Find the memoryLock for this resourceId
108+
item, ok := s.data.locks[req.ResourceId]
109+
110+
if !ok {
111+
return &lock.UnlockResponse{
112+
Status: lock.LOCK_UNEXIST,
113+
}, nil
114+
}
115+
// 2. check the owner information
116+
if item.lock != 1 {
117+
return &lock.UnlockResponse{
118+
Status: lock.LOCK_UNEXIST,
119+
}, nil
120+
}
121+
if item.owner != req.LockOwner {
122+
return &lock.UnlockResponse{
123+
Status: lock.LOCK_BELONG_TO_OTHERS,
124+
}, nil
125+
}
126+
// 3. unlock and reset the owner information
127+
item.owner = ""
128+
item.lock = 0
129+
return &lock.UnlockResponse{
130+
Status: lock.SUCCESS,
131+
}, nil
132+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright 2021 Layotto Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package in_memory
18+
19+
import (
20+
"github.com/stretchr/testify/assert"
21+
"mosn.io/layotto/components/lock"
22+
"testing"
23+
"time"
24+
)
25+
26+
func TestNew(t *testing.T) {
27+
s := NewInMemoryLock()
28+
assert.NotNil(t, s)
29+
}
30+
31+
func TestInit(t *testing.T) {
32+
s := NewInMemoryLock()
33+
assert.NotNil(t, s)
34+
35+
err := s.Init(lock.Metadata{})
36+
assert.NoError(t, err)
37+
}
38+
39+
func TestFeatures(t *testing.T) {
40+
s := NewInMemoryLock()
41+
assert.NotNil(t, s)
42+
43+
f := s.Features()
44+
assert.NotNil(t, f)
45+
assert.Equal(t, 0, len(f))
46+
}
47+
48+
func TestTryLock(t *testing.T) {
49+
s := NewInMemoryLock()
50+
assert.NotNil(t, s)
51+
52+
req := &lock.TryLockRequest{
53+
ResourceId: "key111",
54+
LockOwner: "own",
55+
Expire: 3,
56+
}
57+
58+
var err error
59+
var resp *lock.TryLockResponse
60+
resp, err = s.TryLock(req)
61+
assert.NoError(t, err)
62+
assert.NotNil(t, req)
63+
assert.True(t, resp.Success)
64+
65+
resp, err = s.TryLock(req)
66+
assert.NoError(t, err)
67+
assert.NotNil(t, req)
68+
assert.False(t, resp.Success)
69+
70+
req = &lock.TryLockRequest{
71+
ResourceId: "key112",
72+
LockOwner: "own",
73+
Expire: 1,
74+
}
75+
76+
resp, err = s.TryLock(req)
77+
assert.NoError(t, err)
78+
assert.NotNil(t, req)
79+
assert.True(t, resp.Success)
80+
81+
req = &lock.TryLockRequest{
82+
ResourceId: "key112",
83+
LockOwner: "own",
84+
Expire: 1,
85+
}
86+
87+
resp, err = s.TryLock(req)
88+
assert.NoError(t, err)
89+
assert.NotNil(t, req)
90+
assert.False(t, resp.Success)
91+
92+
s.data.locks["key112"].expireTime = time.Now().Add(-2 * time.Second)
93+
94+
resp, err = s.TryLock(req)
95+
assert.NoError(t, err)
96+
assert.NotNil(t, req)
97+
assert.True(t, resp.Success)
98+
99+
}
100+
101+
func TestUnLock(t *testing.T) {
102+
s := NewInMemoryLock()
103+
assert.NotNil(t, s)
104+
105+
req := &lock.UnlockRequest{
106+
ResourceId: "key111",
107+
LockOwner: "own",
108+
}
109+
110+
var err error
111+
var resp *lock.UnlockResponse
112+
resp, err = s.Unlock(req)
113+
assert.NoError(t, err)
114+
assert.NotNil(t, req)
115+
assert.Equal(t, lock.LOCK_UNEXIST, resp.Status)
116+
117+
lockReq := &lock.TryLockRequest{
118+
ResourceId: "key111",
119+
LockOwner: "own",
120+
Expire: 10,
121+
}
122+
123+
var lockResp *lock.TryLockResponse
124+
lockResp, err = s.TryLock(lockReq)
125+
assert.NoError(t, err)
126+
assert.NotNil(t, req)
127+
assert.True(t, lockResp.Success)
128+
129+
resp, err = s.Unlock(req)
130+
assert.NoError(t, err)
131+
assert.NotNil(t, req)
132+
assert.Equal(t, lock.SUCCESS, resp.Status)
133+
134+
lockResp, err = s.TryLock(lockReq)
135+
assert.NoError(t, err)
136+
assert.NotNil(t, req)
137+
assert.True(t, lockResp.Success)
138+
139+
req.LockOwner = "1"
140+
141+
resp, err = s.Unlock(req)
142+
assert.NoError(t, err)
143+
assert.NotNil(t, req)
144+
assert.Equal(t, lock.LOCK_BELONG_TO_OTHERS, resp.Status)
145+
146+
req.ResourceId = "11"
147+
lockReq.ResourceId = "11"
148+
req.LockOwner = "own1"
149+
lockReq.LockOwner = "own1"
150+
lockResp, err = s.TryLock(lockReq)
151+
assert.NoError(t, err)
152+
assert.NotNil(t, req)
153+
assert.True(t, lockResp.Success)
154+
155+
resp, err = s.Unlock(req)
156+
assert.NoError(t, err)
157+
assert.NotNil(t, req)
158+
assert.Equal(t, lock.SUCCESS, resp.Status)
159+
160+
resp, err = s.Unlock(req)
161+
assert.NoError(t, err)
162+
assert.NotNil(t, req)
163+
assert.Equal(t, lock.LOCK_UNEXIST, resp.Status)
164+
165+
}

configs/config_in_memory.json

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
}
3333
}
3434
},
35+
"lock": {
36+
"in-memory": {
37+
"metadata": {
38+
}
39+
}
40+
},
3541
"pub_subs": {
3642
"in-memory": {
3743
"metadata": {

0 commit comments

Comments
 (0)