-
Notifications
You must be signed in to change notification settings - Fork 109
*: implement LOCK and UNLOCK of tables #448
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,10 @@ func (h *Handler) ConnectionClosed(c *mysql.Conn) { | |
delete(h.c, c.ConnectionID) | ||
h.mu.Unlock() | ||
|
||
if err := h.e.Catalog.UnlockTables(nil, c.ConnectionID); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case the tables would keep locked forever? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it fails, you mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then you can retry |
||
logrus.Errorf("unable to unlock tables on session close: %s", err) | ||
} | ||
|
||
logrus.Infof("ConnectionClosed: client %v", c.ConnectionID) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package sql | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"sync" | ||
|
||
|
@@ -19,14 +20,22 @@ type Catalog struct { | |
mu sync.RWMutex | ||
currentDatabase string | ||
dbs Databases | ||
locks sessionLocks | ||
} | ||
|
||
type ( | ||
sessionLocks map[uint32]dbLocks | ||
dbLocks map[string]tableLocks | ||
tableLocks map[string]struct{} | ||
) | ||
|
||
// NewCatalog returns a new empty Catalog. | ||
func NewCatalog() *Catalog { | ||
return &Catalog{ | ||
FunctionRegistry: NewFunctionRegistry(), | ||
IndexRegistry: NewIndexRegistry(), | ||
ProcessList: NewProcessList(), | ||
locks: make(sessionLocks), | ||
} | ||
} | ||
|
||
|
@@ -124,3 +133,50 @@ func (d Databases) Table(dbName string, tableName string) (Table, error) { | |
|
||
return table, nil | ||
} | ||
|
||
// LockTable adds a lock for the given table and session client. It is assumed | ||
// the database is the current database in use. | ||
func (c *Catalog) LockTable(id uint32, table string) { | ||
db := c.CurrentDatabase() | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
if _, ok := c.locks[id]; !ok { | ||
c.locks[id] = make(dbLocks) | ||
} | ||
|
||
if _, ok := c.locks[id][db]; !ok { | ||
c.locks[id][db] = make(tableLocks) | ||
} | ||
|
||
c.locks[id][db][table] = struct{}{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if we plan (in the future) lock all DB or Session (not only tables, rows), but if yes then maybe flat structure, e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The thing is, we use the elements of each separate step. c.locks[id] to get all the locks for a session and then c.locks[id][db] to get the tables. We can't do that with a single key |
||
} | ||
|
||
// UnlockTables unlocks all tables for which the given session client has a | ||
// lock. | ||
func (c *Catalog) UnlockTables(ctx *Context, id uint32) error { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
|
||
var errors []string | ||
for db, tables := range c.locks[id] { | ||
for t := range tables { | ||
table, err := c.dbs.Table(db, t) | ||
if err == nil { | ||
if lockable, ok := table.(Lockable); ok { | ||
if err := lockable.Unlock(ctx, id); err != nil { | ||
errors = append(errors, err.Error()) | ||
} | ||
} | ||
} else { | ||
errors = append(errors, err.Error()) | ||
} | ||
} | ||
} | ||
|
||
delete(c.locks, id) | ||
if len(errors) > 0 { | ||
return fmt.Errorf("error unlocking tables for %d: %s", id, strings.Join(errors, ", ")) | ||
} | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package sql | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestCatalogLockTable(t *testing.T) { | ||
require := require.New(t) | ||
c := NewCatalog() | ||
c.SetCurrentDatabase("db1") | ||
c.LockTable(1, "foo") | ||
c.LockTable(2, "bar") | ||
c.LockTable(1, "baz") | ||
c.SetCurrentDatabase("db2") | ||
c.LockTable(1, "qux") | ||
|
||
expected := sessionLocks{ | ||
1: dbLocks{ | ||
"db1": tableLocks{ | ||
"foo": struct{}{}, | ||
"baz": struct{}{}, | ||
}, | ||
"db2": tableLocks{ | ||
"qux": struct{}{}, | ||
}, | ||
}, | ||
2: dbLocks{ | ||
"db1": tableLocks{ | ||
"bar": struct{}{}, | ||
}, | ||
}, | ||
} | ||
|
||
require.Equal(expected, c.locks) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we count locks?
Isn't it 1/0 (SET/UNSET) for the same session?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...or is it just counter for number of read/writes for unlock?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is just a counter for the test