mirror of
https://github.com/gogf/gf.git
synced 2025-04-05 11:18:50 +08:00
fix issue in cycle dumping for g.Dump (#2367)
* fix issue in cycle dumping for g.Dump * up * up * up Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
parent
a853984f52
commit
3b245837b9
@ -27,6 +27,7 @@ import (
|
||||
// Queue is a concurrent-safe queue built on doubly linked list and channel.
|
||||
type Queue struct {
|
||||
limit int // Limit for queue size.
|
||||
length *gtype.Int64 // Queue length.
|
||||
list *glist.List // Underlying list structure for data maintaining.
|
||||
closed *gtype.Bool // Whether queue is closed.
|
||||
events chan struct{} // Events for data writing.
|
||||
@ -44,6 +45,7 @@ const (
|
||||
func New(limit ...int) *Queue {
|
||||
q := &Queue{
|
||||
closed: gtype.NewBool(),
|
||||
length: gtype.NewInt64(),
|
||||
}
|
||||
if len(limit) > 0 && limit[0] > 0 {
|
||||
q.limit = limit[0]
|
||||
@ -57,6 +59,57 @@ func New(limit ...int) *Queue {
|
||||
return q
|
||||
}
|
||||
|
||||
// Push pushes the data `v` into the queue.
|
||||
// Note that it would panic if Push is called after the queue is closed.
|
||||
func (q *Queue) Push(v interface{}) {
|
||||
q.length.Add(1)
|
||||
if q.limit > 0 {
|
||||
q.C <- v
|
||||
} else {
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) < defaultQueueSize {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop pops an item from the queue in FIFO way.
|
||||
// Note that it would return nil immediately if Pop is called after the queue is closed.
|
||||
func (q *Queue) Pop() interface{} {
|
||||
item := <-q.C
|
||||
q.length.Add(-1)
|
||||
return item
|
||||
}
|
||||
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *Queue) Close() {
|
||||
q.closed.Set(true)
|
||||
if q.events != nil {
|
||||
close(q.events)
|
||||
}
|
||||
if q.limit > 0 {
|
||||
close(q.C)
|
||||
} else {
|
||||
for i := 0; i < defaultBatchSize; i++ {
|
||||
q.Pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of the queue.
|
||||
// Note that the result might not be accurate as there's an
|
||||
// asynchronous channel reading the list constantly.
|
||||
func (q *Queue) Len() (length int64) {
|
||||
return q.length.Val()
|
||||
}
|
||||
|
||||
// Size is alias of Len.
|
||||
func (q *Queue) Size() int64 {
|
||||
return q.Len()
|
||||
}
|
||||
|
||||
// asyncLoopFromListToChannel starts an asynchronous goroutine,
|
||||
// which handles the data synchronization from list `q.list` to channel `q.C`.
|
||||
func (q *Queue) asyncLoopFromListToChannel() {
|
||||
@ -90,55 +143,3 @@ func (q *Queue) asyncLoopFromListToChannel() {
|
||||
// It's the sender's responsibility to close channel when it should be closed.
|
||||
close(q.C)
|
||||
}
|
||||
|
||||
// Push pushes the data `v` into the queue.
|
||||
// Note that it would panic if Push is called after the queue is closed.
|
||||
func (q *Queue) Push(v interface{}) {
|
||||
if q.limit > 0 {
|
||||
q.C <- v
|
||||
} else {
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) < defaultQueueSize {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pop pops an item from the queue in FIFO way.
|
||||
// Note that it would return nil immediately if Pop is called after the queue is closed.
|
||||
func (q *Queue) Pop() interface{} {
|
||||
return <-q.C
|
||||
}
|
||||
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *Queue) Close() {
|
||||
q.closed.Set(true)
|
||||
if q.events != nil {
|
||||
close(q.events)
|
||||
}
|
||||
if q.limit > 0 {
|
||||
close(q.C)
|
||||
} else {
|
||||
for i := 0; i < defaultBatchSize; i++ {
|
||||
q.Pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the length of the queue.
|
||||
// Note that the result might not be accurate as there's an
|
||||
// asynchronous channel reading the list constantly.
|
||||
func (q *Queue) Len() (length int) {
|
||||
if q.list != nil {
|
||||
length += q.list.Len()
|
||||
}
|
||||
length += len(q.C)
|
||||
return
|
||||
}
|
||||
|
||||
// Size is alias of Len.
|
||||
func (q *Queue) Size() int {
|
||||
return q.Len()
|
||||
}
|
||||
|
@ -39,31 +39,33 @@ func TestRWMutexIsSafe(t *testing.T) {
|
||||
|
||||
func TestSafeRWMutex(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
safeLock := rwmutex.New(true)
|
||||
array := garray.New(true)
|
||||
var (
|
||||
localSafeLock = rwmutex.New(true)
|
||||
array = garray.New(true)
|
||||
)
|
||||
|
||||
go func() {
|
||||
safeLock.Lock()
|
||||
localSafeLock.Lock()
|
||||
array.Append(1)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
safeLock.Unlock()
|
||||
localSafeLock.Unlock()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
safeLock.Lock()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
localSafeLock.Lock()
|
||||
array.Append(1)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
safeLock.Unlock()
|
||||
localSafeLock.Unlock()
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
t.Assert(array.Len(), 1)
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
t.Assert(array.Len(), 3)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 3)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 4)
|
||||
})
|
||||
}
|
||||
@ -77,33 +79,33 @@ func TestSafeReaderRWMutex(t *testing.T) {
|
||||
go func() {
|
||||
localSafeLock.RLock()
|
||||
array.Append(1)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
localSafeLock.RUnlock()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
localSafeLock.RLock()
|
||||
array.Append(1)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(2000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
array.Append(1)
|
||||
localSafeLock.RUnlock()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
localSafeLock.Lock()
|
||||
array.Append(1)
|
||||
localSafeLock.Unlock()
|
||||
}()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 3)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 4)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
t.Assert(array.Len(), 6)
|
||||
})
|
||||
}
|
||||
|
@ -83,9 +83,14 @@ func DumpTo(writer io.Writer, value interface{}, option DumpOption) {
|
||||
type doDumpOption struct {
|
||||
WithType bool
|
||||
ExportedOnly bool
|
||||
DumpedPointerSet map[string]struct{}
|
||||
}
|
||||
|
||||
func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDumpOption) {
|
||||
if option.DumpedPointerSet == nil {
|
||||
option.DumpedPointerSet = map[string]struct{}{}
|
||||
}
|
||||
|
||||
if value == nil {
|
||||
buffer.WriteString(`<nil>`)
|
||||
return
|
||||
@ -111,13 +116,14 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
|
||||
var (
|
||||
reflectKind = reflectValue.Kind()
|
||||
reflectTypeName = reflectValue.Type().String()
|
||||
ptrAddress string
|
||||
newIndent = indent + dumpIndent
|
||||
)
|
||||
reflectTypeName = strings.ReplaceAll(reflectTypeName, `[]uint8`, `[]byte`)
|
||||
if !option.WithType {
|
||||
reflectTypeName = ""
|
||||
}
|
||||
for reflectKind == reflect.Ptr {
|
||||
if ptrAddress == "" {
|
||||
ptrAddress = fmt.Sprintf(`0x%x`, reflectValue.Pointer())
|
||||
}
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
@ -128,9 +134,11 @@ func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDum
|
||||
NewIndent: newIndent,
|
||||
Buffer: buffer,
|
||||
Option: option,
|
||||
PtrAddress: ptrAddress,
|
||||
ReflectValue: reflectValue,
|
||||
ReflectTypeName: reflectTypeName,
|
||||
ExportedOnly: option.ExportedOnly,
|
||||
DumpedPointerSet: option.DumpedPointerSet,
|
||||
}
|
||||
)
|
||||
switch reflectKind {
|
||||
@ -192,7 +200,9 @@ type doDumpInternalInput struct {
|
||||
Option doDumpOption
|
||||
ReflectValue reflect.Value
|
||||
ReflectTypeName string
|
||||
PtrAddress string
|
||||
ExportedOnly bool
|
||||
DumpedPointerSet map[string]struct{}
|
||||
}
|
||||
|
||||
func doDumpSlice(in doDumpInternalInput) {
|
||||
@ -295,6 +305,14 @@ func doDumpMap(in doDumpInternalInput) {
|
||||
}
|
||||
|
||||
func doDumpStruct(in doDumpInternalInput) {
|
||||
if in.PtrAddress != "" {
|
||||
if _, ok := in.DumpedPointerSet[in.PtrAddress]; ok {
|
||||
in.Buffer.WriteString(fmt.Sprintf(`<cycle dump %s>`, in.PtrAddress))
|
||||
return
|
||||
}
|
||||
}
|
||||
in.DumpedPointerSet[in.PtrAddress] = struct{}{}
|
||||
|
||||
structFields, _ := gstructs.Fields(gstructs.FieldsInput{
|
||||
Pointer: in.Value,
|
||||
RecursiveOption: gstructs.RecursiveOptionEmbedded,
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/test/gtest"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
"github.com/gogf/gf/v2/util/gutil"
|
||||
)
|
||||
@ -273,3 +274,17 @@ func Test_Dump_Issue1661(t *testing.T) {
|
||||
]`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Dump_Cycle_Attribute(t *testing.T) {
|
||||
type Abc struct {
|
||||
ab int
|
||||
cd *Abc
|
||||
}
|
||||
abc := Abc{ab: 3}
|
||||
abc.cd = &abc
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
g.DumpTo(buffer, abc, gutil.DumpOption{})
|
||||
t.Assert(gstr.Contains(buffer.String(), "cycle"), true)
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user