浏览代码

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.
Daniel Norberg 11 年之前
父节点
当前提交
20881f1f78
共有 1 个文件被更改,包括 58 次插入43 次删除
  1. 58 43
      gograph/gograph.go

+ 58 - 43
gograph/gograph.go

@@ -218,21 +218,28 @@ func (db *Database) List(name string, depth int) Entities {
 	if err != nil {
 	if err != nil {
 		return out
 		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
 		out[c.FullPath] = c.Entity
 	}
 	}
 	return out
 	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 {
 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 {
 	if err != nil {
 		return err
 		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 {
 		if err := walkFunc(c.FullPath, c.Entity); err != nil {
 			return err
 			return err
 		}
 		}
@@ -240,6 +247,19 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
 	return nil
 	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
 // Return the refrence count for a specified id
 func (db *Database) Refs(id string) int {
 func (db *Database) Refs(id string) int {
 	db.mux.RLock()
 	db.mux.RLock()
@@ -378,56 +398,51 @@ type WalkMeta struct {
 	Edge     *Edge
 	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 {
 	if e == nil {
-		close(out)
-		return out
+		return entities, nil
+	}
+
+	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
+	if err != nil {
+		return nil, err
 	}
 	}
+	defer rows.Close()
 
 
-	go func() {
-		rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
-		if err != nil {
-			close(out)
+	for rows.Next() {
+		var entityId, entityName string
+		if err := rows.Scan(&entityId, &entityName); err != nil {
+			return nil, err
+		}
+		child := &Entity{entityId}
+		edge := &Edge{
+			ParentID: e.id,
+			Name:     entityName,
+			EntityID: child.id,
 		}
 		}
-		defer rows.Close()
 
 
-		for rows.Next() {
-			var entityId, entityName string
-			if err := rows.Scan(&entityId, &entityName); err != nil {
-				// Log error
-				continue
-			}
-			child := &Entity{entityId}
-			edge := &Edge{
-				ParentID: e.id,
-				Name:     entityName,
-				EntityID: child.id,
-			}
+		meta := WalkMeta{
+			Parent:   e,
+			Entity:   child,
+			FullPath: path.Join(name, edge.Name),
+			Edge:     edge,
+		}
 
 
-			meta := WalkMeta{
-				Parent:   e,
-				Entity:   child,
-				FullPath: path.Join(name, edge.Name),
-				Edge:     edge,
-			}
+		entities = append(entities, meta)
 
 
-			out <- meta
-			if depth == 0 {
-				continue
-			}
+		if depth != 0 {
 			nDepth := depth
 			nDepth := depth
 			if depth != -1 {
 			if depth != -1 {
 				nDepth -= 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
 // Return the entity based on the parent path and name