gograph: allow Walk() reentrance
Hold the read lock while reading the child graph, then walk over the children without any lock, in order to avoid deadlock.
This commit is contained in:
parent
04aca7c9e3
commit
20881f1f78
1 changed files with 58 additions and 43 deletions
|
@ -218,21 +218,28 @@ func (db *Database) List(name string, depth int) Entities {
|
|||
if err != nil {
|
||||
return out
|
||||
}
|
||||
for c := range db.children(e, name, depth) {
|
||||
|
||||
children, err := db.children(e, name, depth, nil)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
|
||||
for _, c := range children {
|
||||
out[c.FullPath] = c.Entity
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Walk through the child graph of an entity, calling walkFunc for each child entity.
|
||||
// It is safe for walkFunc to call graph functions.
|
||||
func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
|
||||
db.mux.RLock()
|
||||
defer db.mux.RUnlock()
|
||||
|
||||
e, err := db.get(name)
|
||||
children, err := db.Children(name, depth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for c := range db.children(e, name, depth) {
|
||||
|
||||
// Note: the database lock must not be held while calling walkFunc
|
||||
for _, c := range children {
|
||||
if err := walkFunc(c.FullPath, c.Entity); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -240,6 +247,19 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Return the children of the specified entity
|
||||
func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
|
||||
db.mux.RLock()
|
||||
defer db.mux.RUnlock()
|
||||
|
||||
e, err := db.get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db.children(e, name, depth, nil)
|
||||
}
|
||||
|
||||
// Return the refrence count for a specified id
|
||||
func (db *Database) Refs(id string) int {
|
||||
db.mux.RLock()
|
||||
|
@ -378,25 +398,21 @@ type WalkMeta struct {
|
|||
Edge *Edge
|
||||
}
|
||||
|
||||
func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta {
|
||||
out := make(chan WalkMeta)
|
||||
func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
|
||||
if e == nil {
|
||||
close(out)
|
||||
return out
|
||||
return entities, nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
|
||||
if err != nil {
|
||||
close(out)
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var entityId, entityName string
|
||||
if err := rows.Scan(&entityId, &entityName); err != nil {
|
||||
// Log error
|
||||
continue
|
||||
return nil, err
|
||||
}
|
||||
child := &Entity{entityId}
|
||||
edge := &Edge{
|
||||
|
@ -412,22 +428,21 @@ func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta
|
|||
Edge: edge,
|
||||
}
|
||||
|
||||
out <- meta
|
||||
if depth == 0 {
|
||||
continue
|
||||
}
|
||||
entities = append(entities, meta)
|
||||
|
||||
if depth != 0 {
|
||||
nDepth := depth
|
||||
if depth != -1 {
|
||||
nDepth -= 1
|
||||
}
|
||||
sc := db.children(child, meta.FullPath, nDepth)
|
||||
for c := range sc {
|
||||
out <- c
|
||||
entities, err = db.children(child, meta.FullPath, nDepth, entities)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
close(out)
|
||||
}()
|
||||
return out
|
||||
}
|
||||
|
||||
return entities, nil
|
||||
}
|
||||
|
||||
// Return the entity based on the parent path and name
|
||||
|
|
Loading…
Reference in a new issue