Merge branch 'master' into 完善garray测试

This commit is contained in:
jroam 2019-06-25 09:34:17 +08:00
commit 3bfff2347f
53 changed files with 2759 additions and 719 deletions

1
.gitignore vendored
View File

@ -7,7 +7,6 @@
.settings/ .settings/
.vscode/ .vscode/
vender/ vender/
log/
composer.lock composer.lock
gitpush.sh gitpush.sh
pkg/ pkg/

View File

@ -4,7 +4,7 @@
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf/g#pkg-subdirectories) [![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf/g#pkg-subdirectories)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf) [![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf) [![Go Report](https://goreportcard.com/badge/github.com/gogf/gf?v=1)](https://goreportcard.com/report/github.com/gogf/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master) [![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf) [![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf) [![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)

View File

@ -9,11 +9,12 @@ package garray
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math"
"sort"
"github.com/gogf/gf/g/internal/rwmutex" "github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand" "github.com/gogf/gf/g/util/grand"
"math"
"sort"
) )
type IntArray struct { type IntArray struct {
@ -266,31 +267,83 @@ func (a *IntArray) PopRights(size int) []int {
// Range picks and returns items by range, like array[start:end]. // Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice; // Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data. // else a pointer to the underlying data.
func (a *IntArray) Range(start, end int) []int { //
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *IntArray) Range(start int, end ...int) []int {
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() defer a.mu.RUnlock()
length := len(a.array) offsetEnd := len(a.array)
if start > length || start > end { if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil return nil
} }
if start < 0 { if start < 0 {
start = 0 start = 0
} }
if end > length {
end = length
}
array := ([]int)(nil) array := ([]int)(nil)
if a.mu.IsSafe() { if a.mu.IsSafe() {
a.mu.RLock() array = make([]int, offsetEnd-start)
defer a.mu.RUnlock() copy(array, a.array[start:offsetEnd])
array = make([]int, end-start)
copy(array, a.array[start:end])
} else { } else {
array = a.array[start:end] array = a.array[start:offsetEnd]
} }
return array return array
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *IntArray) SubSlice(offset int, length ...int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// See PushRight. // See PushRight.
func (a *IntArray) Append(value ...int) *IntArray { func (a *IntArray) Append(value ...int) *IntArray {
a.mu.Lock() a.mu.Lock()
@ -488,27 +541,6 @@ func (a *IntArray) Pad(size int, value int) *IntArray {
return a return a
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *IntArray) SubSlice(offset, size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset+size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting). // Rand randomly returns one item from array(no deleting).
func (a *IntArray) Rand() int { func (a *IntArray) Rand() int {
a.mu.RLock() a.mu.RLock()

View File

@ -9,11 +9,12 @@ package garray
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math"
"sort"
"github.com/gogf/gf/g/internal/rwmutex" "github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand" "github.com/gogf/gf/g/util/grand"
"math"
"sort"
) )
type Array struct { type Array struct {
@ -262,31 +263,83 @@ func (a *Array) PopRights(size int) []interface{} {
// Range picks and returns items by range, like array[start:end]. // Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice; // Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data. // else a pointer to the underlying data.
func (a *Array) Range(start, end int) []interface{} { //
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *Array) Range(start int, end ...int) []interface{} {
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() defer a.mu.RUnlock()
length := len(a.array) offsetEnd := len(a.array)
if start > length || start > end { if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil return nil
} }
if start < 0 { if start < 0 {
start = 0 start = 0
} }
if end > length {
end = length
}
array := ([]interface{})(nil) array := ([]interface{})(nil)
if a.mu.IsSafe() { if a.mu.IsSafe() {
a.mu.RLock() array = make([]interface{}, offsetEnd-start)
defer a.mu.RUnlock() copy(array, a.array[start:offsetEnd])
array = make([]interface{}, end-start)
copy(array, a.array[start:end])
} else { } else {
array = a.array[start:end] array = a.array[start:offsetEnd]
} }
return array return array
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *Array) SubSlice(offset int, length ...int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]interface{}, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// See PushRight. // See PushRight.
func (a *Array) Append(value ...interface{}) *Array { func (a *Array) Append(value ...interface{}) *Array {
a.PushRight(value...) a.PushRight(value...)
@ -482,27 +535,6 @@ func (a *Array) Pad(size int, val interface{}) *Array {
return a return a
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *Array) SubSlice(offset, size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset+size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]interface{}, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting). // Rand randomly returns one item from array(no deleting).
func (a *Array) Rand() interface{} { func (a *Array) Rand() interface{} {
a.mu.RLock() a.mu.RLock()

View File

@ -9,12 +9,13 @@ package garray
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math" "math"
"sort" "sort"
"strings" "strings"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
) )
type StringArray struct { type StringArray struct {
@ -267,31 +268,83 @@ func (a *StringArray) PopRights(size int) []string {
// Range picks and returns items by range, like array[start:end]. // Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice; // Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data. // else a pointer to the underlying data.
func (a *StringArray) Range(start, end int) []string { //
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *StringArray) Range(start int, end ...int) []string {
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() defer a.mu.RUnlock()
length := len(a.array) offsetEnd := len(a.array)
if start > length || start > end { if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil return nil
} }
if start < 0 { if start < 0 {
start = 0 start = 0
} }
if end > length {
end = length
}
array := ([]string)(nil) array := ([]string)(nil)
if a.mu.IsSafe() { if a.mu.IsSafe() {
a.mu.RLock() array = make([]string, offsetEnd-start)
defer a.mu.RUnlock() copy(array, a.array[start:offsetEnd])
array = make([]string, end-start)
copy(array, a.array[start:end])
} else { } else {
array = a.array[start:end] array = a.array[start:offsetEnd]
} }
return array return array
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *StringArray) SubSlice(offset int, length ...int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// See PushRight. // See PushRight.
func (a *StringArray) Append(value ...string) *StringArray { func (a *StringArray) Append(value ...string) *StringArray {
a.mu.Lock() a.mu.Lock()
@ -488,27 +541,6 @@ func (a *StringArray) Pad(size int, value string) *StringArray {
return a return a
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *StringArray) SubSlice(offset, size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset+size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting). // Rand randomly returns one item from array(no deleting).
func (a *StringArray) Rand() string { func (a *StringArray) Rand() string {
a.mu.RLock() a.mu.RLock()

View File

@ -9,12 +9,13 @@ package garray
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math"
"sort"
"github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex" "github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand" "github.com/gogf/gf/g/util/grand"
"math"
"sort"
) )
// It's using increasing order in default. // It's using increasing order in default.
@ -216,31 +217,83 @@ func (a *SortedIntArray) PopRights(size int) []int {
// Range picks and returns items by range, like array[start:end]. // Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice; // Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data. // else a pointer to the underlying data.
func (a *SortedIntArray) Range(start, end int) []int { //
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedIntArray) Range(start int, end ...int) []int {
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() defer a.mu.RUnlock()
length := len(a.array) offsetEnd := len(a.array)
if start > length || start > end { if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil return nil
} }
if start < 0 { if start < 0 {
start = 0 start = 0
} }
if end > length {
end = length
}
array := ([]int)(nil) array := ([]int)(nil)
if a.mu.IsSafe() { if a.mu.IsSafe() {
a.mu.RLock() array = make([]int, offsetEnd-start)
defer a.mu.RUnlock() copy(array, a.array[start:offsetEnd])
array = make([]int, end-start)
copy(array, a.array[start:end])
} else { } else {
array = a.array[start:end] array = a.array[start:offsetEnd]
} }
return array return array
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *SortedIntArray) SubSlice(offset int, length ...int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// Len returns the length of array. // Len returns the length of array.
func (a *SortedIntArray) Len() int { func (a *SortedIntArray) Len() int {
a.mu.RLock() a.mu.RLock()
@ -433,27 +486,6 @@ func (a *SortedIntArray) Chunk(size int) [][]int {
return n return n
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *SortedIntArray) SubSlice(offset, size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset+size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting). // Rand randomly returns one item from array(no deleting).
func (a *SortedIntArray) Rand() int { func (a *SortedIntArray) Rand() int {
a.mu.RLock() a.mu.RLock()

View File

@ -9,12 +9,13 @@ package garray
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math"
"sort"
"github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex" "github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand" "github.com/gogf/gf/g/util/grand"
"math"
"sort"
) )
// It's using increasing order in default. // It's using increasing order in default.
@ -217,31 +218,83 @@ func (a *SortedArray) PopRights(size int) []interface{} {
// Range picks and returns items by range, like array[start:end]. // Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice; // Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data. // else a pointer to the underlying data.
func (a *SortedArray) Range(start, end int) []interface{} { //
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedArray) Range(start int, end ...int) []interface{} {
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() defer a.mu.RUnlock()
length := len(a.array) offsetEnd := len(a.array)
if start > length || start > end { if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil return nil
} }
if start < 0 { if start < 0 {
start = 0 start = 0
} }
if end > length {
end = length
}
array := ([]interface{})(nil) array := ([]interface{})(nil)
if a.mu.IsSafe() { if a.mu.IsSafe() {
a.mu.RLock() array = make([]interface{}, offsetEnd-start)
defer a.mu.RUnlock() copy(array, a.array[start:offsetEnd])
array = make([]interface{}, end-start)
copy(array, a.array[start:end])
} else { } else {
array = a.array[start:end] array = a.array[start:offsetEnd]
} }
return array return array
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *SortedArray) SubSlice(offset int, length ...int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]interface{}, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// Sum returns the sum of values in an array. // Sum returns the sum of values in an array.
func (a *SortedArray) Sum() (sum int) { func (a *SortedArray) Sum() (sum int) {
a.mu.RLock() a.mu.RLock()
@ -434,27 +487,6 @@ func (a *SortedArray) Chunk(size int) [][]interface{} {
return n return n
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *SortedArray) SubSlice(offset, size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset+size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]interface{}, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting). // Rand randomly returns one item from array(no deleting).
func (a *SortedArray) Rand() interface{} { func (a *SortedArray) Rand() interface{} {
a.mu.RLock() a.mu.RLock()

View File

@ -9,13 +9,14 @@ package garray
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math"
"sort"
"strings"
"github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex" "github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand" "github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
) )
// It's using increasing order in default. // It's using increasing order in default.
@ -211,31 +212,83 @@ func (a *SortedStringArray) PopRights(size int) []string {
// Range picks and returns items by range, like array[start:end]. // Range picks and returns items by range, like array[start:end].
// Notice, if in concurrent-safe usage, it returns a copy of slice; // Notice, if in concurrent-safe usage, it returns a copy of slice;
// else a pointer to the underlying data. // else a pointer to the underlying data.
func (a *SortedStringArray) Range(start, end int) []string { //
// If <end> is negative, then the offset will start from the end of array.
// If <end> is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *SortedStringArray) Range(start int, end ...int) []string {
a.mu.RLock() a.mu.RLock()
defer a.mu.RUnlock() defer a.mu.RUnlock()
length := len(a.array) offsetEnd := len(a.array)
if start > length || start > end { if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil return nil
} }
if start < 0 { if start < 0 {
start = 0 start = 0
} }
if end > length {
end = length
}
array := ([]string)(nil) array := ([]string)(nil)
if a.mu.IsSafe() { if a.mu.IsSafe() {
a.mu.RLock() array = make([]string, offsetEnd-start)
defer a.mu.RUnlock() copy(array, a.array[start:offsetEnd])
array = make([]string, end-start)
copy(array, a.array[start:end])
} else { } else {
array = a.array[start:end] array = a.array[start:offsetEnd]
} }
return array return array
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
//
// If offset is non-negative, the sequence will start at that offset in the array.
// If offset is negative, the sequence will start that far from the end of the array.
//
// If length is given and is positive, then the sequence will have up to that many elements in it.
// If the array is shorter than the length, then only the available array elements will be present.
// If length is given and is negative then the sequence will stop that many elements from the end of the array.
// If it is omitted, then the sequence will have everything from offset up until the end of the array.
//
// Any possibility crossing the left border of array, it will fail.
func (a *SortedStringArray) SubSlice(offset int, length ...int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// Sum returns the sum of values in an array. // Sum returns the sum of values in an array.
func (a *SortedStringArray) Sum() (sum int) { func (a *SortedStringArray) Sum() (sum int) {
a.mu.RLock() a.mu.RLock()
@ -428,27 +481,6 @@ func (a *SortedStringArray) Chunk(size int) [][]string {
return n return n
} }
// SubSlice returns a slice of elements from the array as specified
// by the <offset> and <size> parameters.
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
func (a *SortedStringArray) SubSlice(offset, size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if offset > len(a.array) {
return nil
}
if offset+size > len(a.array) {
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:]
}
}
// Rand randomly returns one item from array(no deleting). // Rand randomly returns one item from array(no deleting).
func (a *SortedStringArray) Rand() string { func (a *SortedStringArray) Rand() string {
a.mu.RLock() a.mu.RLock()

View File

@ -11,10 +11,7 @@ package garray_test
import ( import (
"github.com/gogf/gf/g/container/garray" "github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/test/gtest" "github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"strings"
"testing" "testing"
"time"
) )
func Test_IntArray_Basic(t *testing.T) { func Test_IntArray_Basic(t *testing.T) {
@ -49,7 +46,7 @@ func TestIntArray_Sort(t *testing.T) {
expect1 := []int{0, 1, 2, 3} expect1 := []int{0, 1, 2, 3}
expect2 := []int{3, 2, 1, 0} expect2 := []int{3, 2, 1, 0}
array := garray.NewIntArray() array := garray.NewIntArray()
array2:=garray.NewIntArray(true) array2 := garray.NewIntArray(true)
for i := 3; i >= 0; i-- { for i := 3; i >= 0; i-- {
array.Append(i) array.Append(i)
array2.Append(i) array2.Append(i)
@ -62,7 +59,6 @@ func TestIntArray_Sort(t *testing.T) {
array2.Sort(true) array2.Sort(true)
gtest.Assert(array2.Slice(), expect2) gtest.Assert(array2.Slice(), expect2)
}) })
} }
@ -110,10 +106,11 @@ func TestIntArray_Range(t *testing.T) {
gtest.Case(t, func() { gtest.Case(t, func() {
value1 := []int{0, 1, 2, 3, 4, 5, 6} value1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(value1) array1 := garray.NewIntArrayFrom(value1)
array2 := garray.NewIntArrayFrom(value1,true) array2 := garray.NewIntArrayFrom(value1, true)
gtest.Assert(array1.Range(0, 1), []int{0}) gtest.Assert(array1.Range(0, 1), []int{0})
gtest.Assert(array1.Range(1, 2), []int{1}) gtest.Assert(array1.Range(1, 2), []int{1})
gtest.Assert(array1.Range(0, 2), []int{0, 1}) gtest.Assert(array1.Range(0, 2), []int{0, 1})
gtest.Assert(array1.Range(10, 2), nil)
gtest.Assert(array1.Range(-1, 10), value1) gtest.Assert(array1.Range(-1, 10), value1)
gtest.Assert(array1.Range(8, 2), nil) gtest.Assert(array1.Range(8, 2), nil)
@ -124,32 +121,32 @@ func TestIntArray_Range(t *testing.T) {
func TestIntArray_Merge(t *testing.T) { func TestIntArray_Merge(t *testing.T) {
gtest.Case(t, func() { gtest.Case(t, func() {
n1 := []int{1, 2, 4, 3} n1 := []int{1, 2, 4, 3}
n2:=[]int{7,8,9} n2 := []int{7, 8, 9}
n3:=[]int{3,6} n3 := []int{3, 6}
s1:=[]string{"a","b","c"} s1 := []string{"a", "b", "c"}
in1:=[]interface{}{1,"a",2,"b"} in1 := []interface{}{1, "a", 2, "b"}
func1:=func(v1,v2 interface{})int{ func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2)) return strings.Compare(gconv.String(v1), gconv.String(v2))
} }
a1 := garray.NewIntArrayFrom(n1) a1 := garray.NewIntArrayFrom(n1)
b1 := garray.NewStringArrayFrom(s1) b1 := garray.NewStringArrayFrom(s1)
b2:=garray.NewIntArrayFrom(n3) b2 := garray.NewIntArrayFrom(n3)
b3:=garray.NewArrayFrom(in1) b3 := garray.NewArrayFrom(in1)
b4:=garray.NewSortedStringArrayFrom(s1) b4 := garray.NewSortedStringArrayFrom(s1)
b5:=garray.NewSortedIntArrayFrom(n3) b5 := garray.NewSortedIntArrayFrom(n3)
b6:=garray.NewSortedArrayFrom(in1,func1) b6 := garray.NewSortedArrayFrom(in1, func1)
gtest.Assert(a1.Merge(n2).Len(),7) gtest.Assert(a1.Merge(n2).Len(), 7)
gtest.Assert(a1.Merge(n3).Len(),9) gtest.Assert(a1.Merge(n3).Len(), 9)
gtest.Assert(a1.Merge(b1).Len(),12) gtest.Assert(a1.Merge(b1).Len(), 12)
gtest.Assert(a1.Merge(b2).Len(),14) gtest.Assert(a1.Merge(b2).Len(), 14)
gtest.Assert(a1.Merge(b3).Len(),18) gtest.Assert(a1.Merge(b3).Len(), 18)
gtest.Assert(a1.Merge(b4).Len(),21) gtest.Assert(a1.Merge(b4).Len(), 21)
gtest.Assert(a1.Merge(b5).Len(),23) gtest.Assert(a1.Merge(b5).Len(), 23)
gtest.Assert(a1.Merge(b6).Len(),27) gtest.Assert(a1.Merge(b6).Len(), 27)
}) })
} }
@ -192,10 +189,21 @@ func TestIntArray_SubSlice(t *testing.T) {
gtest.Case(t, func() { gtest.Case(t, func() {
a1 := []int{0, 1, 2, 3, 4, 5, 6} a1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(a1) array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(array1.SubSlice(6), []int{6})
gtest.Assert(array1.SubSlice(5), []int{5, 6})
gtest.Assert(array1.SubSlice(8), nil)
gtest.Assert(array1.SubSlice(0, 2), []int{0, 1}) gtest.Assert(array1.SubSlice(0, 2), []int{0, 1})
gtest.Assert(array1.SubSlice(2, 2), []int{2, 3}) gtest.Assert(array1.SubSlice(2, 2), []int{2, 3})
gtest.Assert(array1.SubSlice(5, 8), []int{5, 6}) gtest.Assert(array1.SubSlice(5, 8), []int{5, 6})
gtest.Assert(array1.SubSlice(8, 2), nil) gtest.Assert(array1.SubSlice(-1, 1), []int{6})
gtest.Assert(array1.SubSlice(-1, 9), []int{6})
gtest.Assert(array1.SubSlice(-2, 3), []int{5, 6})
gtest.Assert(array1.SubSlice(-7, 3), []int{0, 1, 2})
gtest.Assert(array1.SubSlice(-8, 3), nil)
gtest.Assert(array1.SubSlice(-1, -3), []int{3, 4, 5})
gtest.Assert(array1.SubSlice(-9, 3), nil)
gtest.Assert(array1.SubSlice(1, -1), []int{0})
gtest.Assert(array1.SubSlice(1, -3), nil)
}) })
} }
@ -417,7 +425,7 @@ func TestSortedIntArray_Range(t *testing.T) {
gtest.Case(t, func() { gtest.Case(t, func() {
a1 := []int{1, 3, 5, 2, 6, 7} a1 := []int{1, 3, 5, 2, 6, 7}
array1 := garray.NewSortedIntArrayFrom(a1) array1 := garray.NewSortedIntArrayFrom(a1)
array2 := garray.NewSortedIntArrayFrom(a1,true) array2 := garray.NewSortedIntArrayFrom(a1, true)
ns1 := array1.Range(1, 4) ns1 := array1.Range(1, 4)
gtest.Assert(len(ns1), 3) gtest.Assert(len(ns1), 3)
gtest.Assert(ns1, []int{2, 3, 5}) gtest.Assert(ns1, []int{2, 3, 5})
@ -482,7 +490,6 @@ func TestSortedIntArray_Chunk(t *testing.T) {
array1 := garray.NewSortedIntArrayFrom(a1) array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.Chunk(2) //按每几个元素切成一个数组 ns1 := array1.Chunk(2) //按每几个元素切成一个数组
ns2 := array1.Chunk(-1) ns2 := array1.Chunk(-1)
t.Log(ns1)
gtest.Assert(len(ns1), 3) gtest.Assert(len(ns1), 3)
gtest.Assert(ns1[0], []int{1, 2}) gtest.Assert(ns1[0], []int{1, 2})
gtest.Assert(ns1[2], []int{5}) gtest.Assert(ns1[2], []int{5})
@ -711,37 +718,34 @@ func TestSortedIntArray_RLockFunc(t *testing.T) {
gtest.Assert(a1.Contains(7), true) gtest.Assert(a1.Contains(7), true)
} }
func TestSortedIntArray_Merge(t *testing.T) { func TestSortedIntArray_Merge(t *testing.T) {
n1 := []int{1, 2, 4, 3} n1 := []int{1, 2, 4, 3}
n2:=[]int{7,8,9} n2 := []int{7, 8, 9}
n3:=[]int{3,6} n3 := []int{3, 6}
s1:=[]string{"a","b","c"}
in1:=[]interface{}{1,"a",2,"b"}
s1 := []string{"a", "b", "c"}
in1 := []interface{}{1, "a", 2, "b"}
a1 := garray.NewSortedIntArrayFrom(n1) a1 := garray.NewSortedIntArrayFrom(n1)
b1 := garray.NewStringArrayFrom(s1) b1 := garray.NewStringArrayFrom(s1)
b2:=garray.NewIntArrayFrom(n3) b2 := garray.NewIntArrayFrom(n3)
b3:=garray.NewArrayFrom(in1) b3 := garray.NewArrayFrom(in1)
b4:=garray.NewSortedStringArrayFrom(s1) b4 := garray.NewSortedStringArrayFrom(s1)
b5:=garray.NewSortedIntArrayFrom(n3) b5 := garray.NewSortedIntArrayFrom(n3)
gtest.Assert(a1.Merge(n2).Len(), 7)
gtest.Assert(a1.Merge(n2).Len(),7) gtest.Assert(a1.Merge(n3).Len(), 9)
gtest.Assert(a1.Merge(n3).Len(),9) gtest.Assert(a1.Merge(b1).Len(), 12)
gtest.Assert(a1.Merge(b1).Len(),12) gtest.Assert(a1.Merge(b2).Len(), 14)
gtest.Assert(a1.Merge(b2).Len(),14) gtest.Assert(a1.Merge(b3).Len(), 18)
gtest.Assert(a1.Merge(b3).Len(),18) gtest.Assert(a1.Merge(b4).Len(), 21)
gtest.Assert(a1.Merge(b4).Len(),21) gtest.Assert(a1.Merge(b5).Len(), 23)
gtest.Assert(a1.Merge(b5).Len(),23)
} }
func TestSortedArray_LockFunc(t *testing.T) { func TestSortedArray_LockFunc(t *testing.T) {
n1 := []interface{}{1, 2, 4, 3} n1 := []interface{}{1, 2, 4, 3}
func1:=func(v1,v2 interface{})int{ func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2)) return strings.Compare(gconv.String(v1), gconv.String(v2))
} }
a1 := garray.NewSortedArrayFrom(n1, func1) a1 := garray.NewSortedArrayFrom(n1, func1)
@ -769,7 +773,7 @@ func TestSortedArray_LockFunc(t *testing.T) {
func TestSortedArray_RLockFunc(t *testing.T) { func TestSortedArray_RLockFunc(t *testing.T) {
n1 := []interface{}{1, 2, 4, 3} n1 := []interface{}{1, 2, 4, 3}
func1:=func(v1,v2 interface{})int{ func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2)) return strings.Compare(gconv.String(v1), gconv.String(v2))
} }
a1 := garray.NewSortedArrayFrom(n1, func1) a1 := garray.NewSortedArrayFrom(n1, func1)
@ -795,56 +799,54 @@ func TestSortedArray_RLockFunc(t *testing.T) {
gtest.Assert(a1.Contains(7), true) gtest.Assert(a1.Contains(7), true)
} }
func TestSortedArray_Merge(t *testing.T) { func TestSortedArray_Merge(t *testing.T) {
n1 := []interface{}{1, 2, 4, 3} n1 := []interface{}{1, 2, 4, 3}
n2:=[]int{7,8,9} n2 := []int{7, 8, 9}
n3:=[]int{3,6} n3 := []int{3, 6}
s1:=[]string{"a","b","c"} s1 := []string{"a", "b", "c"}
in1:=[]interface{}{1,"a",2,"b"} in1 := []interface{}{1, "a", 2, "b"}
func1:=func(v1,v2 interface{})int{ func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2)) return strings.Compare(gconv.String(v1), gconv.String(v2))
} }
a1 := garray.NewSortedArrayFrom(n1,func1) a1 := garray.NewSortedArrayFrom(n1, func1)
b1 := garray.NewStringArrayFrom(s1) b1 := garray.NewStringArrayFrom(s1)
b2:=garray.NewIntArrayFrom(n3) b2 := garray.NewIntArrayFrom(n3)
b3:=garray.NewArrayFrom(in1) b3 := garray.NewArrayFrom(in1)
b4:=garray.NewSortedStringArrayFrom(s1) b4 := garray.NewSortedStringArrayFrom(s1)
b5:=garray.NewSortedIntArrayFrom(n3) b5 := garray.NewSortedIntArrayFrom(n3)
gtest.Assert(a1.Merge(n2).Len(),7) gtest.Assert(a1.Merge(n2).Len(), 7)
gtest.Assert(a1.Merge(n3).Len(),9) gtest.Assert(a1.Merge(n3).Len(), 9)
gtest.Assert(a1.Merge(b1).Len(),12) gtest.Assert(a1.Merge(b1).Len(), 12)
gtest.Assert(a1.Merge(b2).Len(),14) gtest.Assert(a1.Merge(b2).Len(), 14)
gtest.Assert(a1.Merge(b3).Len(),18) gtest.Assert(a1.Merge(b3).Len(), 18)
gtest.Assert(a1.Merge(b4).Len(),21) gtest.Assert(a1.Merge(b4).Len(), 21)
gtest.Assert(a1.Merge(b5).Len(),23) gtest.Assert(a1.Merge(b5).Len(), 23)
} }
func TestIntArray_SortFunc(t *testing.T) { func TestIntArray_SortFunc(t *testing.T) {
n1:=[]int{1,2,3,5,4} n1 := []int{1, 2, 3, 5, 4}
a1:=garray.NewIntArrayFrom(n1) a1 := garray.NewIntArrayFrom(n1)
func1:=func(v1,v2 int)bool{ func1 := func(v1, v2 int) bool {
if v1>v2{ if v1 > v2 {
return false return false
} }
return true return true
} }
func2:=func(v1,v2 int)bool{ func2 := func(v1, v2 int) bool {
if v1>v2{ if v1 > v2 {
return true return true
} }
return true return true
} }
a2:=a1.SortFunc(func1) a2 := a1.SortFunc(func1)
gtest.Assert(a2,[]int{1,2,3,4,5}) gtest.Assert(a2, []int{1, 2, 3, 4, 5})
a3:=a1.SortFunc(func2) a3 := a1.SortFunc(func2)
gtest.Assert(a3,[]int{5,4,3,2,1}) gtest.Assert(a3, []int{5, 4, 3, 2, 1})
} }
@ -895,4 +897,3 @@ func TestIntArray_RLockFunc(t *testing.T) {
gtest.AssertLT(t2-t1, 20) gtest.AssertLT(t2-t1, 20)
gtest.Assert(a1.Contains(7), true) gtest.Assert(a1.Contains(7), true)
} }

View File

@ -9,11 +9,12 @@ package gpool
import ( import (
"errors" "errors"
"time"
"github.com/gogf/gf/g/container/glist" "github.com/gogf/gf/g/container/glist"
"github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gtimer" "github.com/gogf/gf/g/os/gtimer"
"time"
) )
// Object-Reusable Pool. // Object-Reusable Pool.
@ -126,6 +127,7 @@ func (p *Pool) checkExpire() {
gtimer.Exit() gtimer.Exit()
} }
for { for {
// TODO Do not use Pop and Push mechanism, which is not graceful.
if r := p.list.PopFront(); r != nil { if r := p.list.PopFront(); r != nil {
item := r.(*poolItem) item := r.(*poolItem)
if item.expire == 0 || item.expire > gtime.Millisecond() { if item.expire == 0 || item.expire > gtime.Millisecond() {

View File

@ -41,7 +41,7 @@ func (v *Int64) Val() int64 {
} }
// Add atomically adds <delta> to t.value and returns the new value. // Add atomically adds <delta> to t.value and returns the new value.
func (v *Int64) Add(delta int64) int64 { func (v *Int64) Add(delta int64) (new int64) {
return atomic.AddInt64(&v.value, delta) return atomic.AddInt64(&v.value, delta)
} }

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/g/crypto/gmd5" "github.com/gogf/gf/g/crypto/gmd5"
"github.com/gogf/gf/g/crypto/gcrc32" "github.com/gogf/gf/g/crypto/gcrc32"
"github.com/gogf/gf/g/crypto/gmd5"
"github.com/gogf/gf/g/test/gtest" "github.com/gogf/gf/g/test/gtest"
) )

View File

@ -11,10 +11,10 @@ import (
"crypto/md5" "crypto/md5"
"errors" "errors"
"fmt" "fmt"
"github.com/gogf/gf/g/internal/errors"
"github.com/gogf/gf/g/util/gconv"
"io" "io"
"os" "os"
"github.com/gogf/gf/g/util/gconv"
) )
// Encrypt encrypts any type of variable using MD5 algorithms. // Encrypt encrypts any type of variable using MD5 algorithms.
@ -40,9 +40,7 @@ func EncryptFile(path string) (encrypt string, err error) {
return "", err return "", err
} }
defer func() { defer func() {
if e := f.Close(); e != nil { err = errors.Wrap(f.Close(), "file closing error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
h := md5.New() h := md5.New()
_, err = io.Copy(h, f) _, err = io.Copy(h, f)

View File

@ -10,11 +10,10 @@ package gsha1
import ( import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"errors" "github.com/gogf/gf/g/internal/errors"
"github.com/gogf/gf/g/util/gconv"
"io" "io"
"os" "os"
"github.com/gogf/gf/g/util/gconv"
) )
// Encrypt encrypts any type of variable using SHA1 algorithms. // Encrypt encrypts any type of variable using SHA1 algorithms.
@ -37,9 +36,7 @@ func EncryptFile(path string) (encrypt string, err error) {
return "", err return "", err
} }
defer func() { defer func() {
if e := f.Close(); e != nil { err = errors.Wrap(f.Close(), "file closing error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
h := sha1.New() h := sha1.New()
_, err = io.Copy(h, f) _, err = io.Copy(h, f)

View File

@ -7,9 +7,10 @@
package gdb_test package gdb_test
import ( import (
"testing"
"github.com/gogf/gf/g/database/gdb" "github.com/gogf/gf/g/database/gdb"
"github.com/gogf/gf/g/test/gtest" "github.com/gogf/gf/g/test/gtest"
"testing"
) )
func Test_Instance(t *testing.T) { func Test_Instance(t *testing.T) {

View File

@ -8,12 +8,13 @@
package gjson package gjson
import ( import (
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
) )
const ( const (
@ -26,8 +27,14 @@ type Json struct {
mu *rwmutex.RWMutex mu *rwmutex.RWMutex
p *interface{} // Pointer for hierarchical data access, it's the root of data in default. p *interface{} // Pointer for hierarchical data access, it's the root of data in default.
c byte // Char separator('.' in default). c byte // Char separator('.' in default).
vc bool // Violence Check(false in default), which is used to access data // Violence Check(false in default), which is used to access data
// when the hierarchical data key contains separator char. // when the hierarchical data key contains separator char.
vc bool
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (j *Json) MarshalJSON() ([]byte, error) {
return j.ToJson()
} }
// Set <value> by <pattern>. // Set <value> by <pattern>.

View File

@ -6,6 +6,11 @@
package gparser package gparser
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (p *Parser) MarshalJSON() ([]byte, error) {
return p.json.MarshalJSON()
}
func (p *Parser) ToXml(rootTag ...string) ([]byte, error) { func (p *Parser) ToXml(rootTag ...string) ([]byte, error) {
return p.json.ToXml(rootTag...) return p.json.ToXml(rootTag...)
} }

View File

@ -8,11 +8,12 @@ package gins_test
import ( import (
"fmt" "fmt"
"testing"
"github.com/gogf/gf/g/frame/gins" "github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile" "github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gtime" "github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest" "github.com/gogf/gf/g/test/gtest"
"testing"
) )
func Test_View(t *testing.T) { func Test_View(t *testing.T) {

2
g/g.go
View File

@ -39,6 +39,8 @@ type Slice = []interface{}
type SliceAny = []interface{} type SliceAny = []interface{}
type SliceStr = []string type SliceStr = []string
type SliceInt = []int type SliceInt = []int
// Array is alias of Slice.
type Array = []interface{} type Array = []interface{}
type ArrayAny = []interface{} type ArrayAny = []interface{}
type ArrayStr = []string type ArrayStr = []string

View File

@ -39,7 +39,7 @@ func Throw(exception interface{}) {
gutil.Throw(exception) gutil.Throw(exception)
} }
// TryCatch does the try...catch... logic. // TryCatch does the try...catch... mechanism.
func TryCatch(try func(), catch ...func(exception interface{})) { func TryCatch(try func(), catch ...func(exception interface{})) {
gutil.TryCatch(try, catch...) gutil.TryCatch(try, catch...)
} }

View File

@ -10,7 +10,7 @@ import (
"github.com/gogf/gf/g/os/glog" "github.com/gogf/gf/g/os/glog"
) )
// SetDebug disables/enables debug level for logging globally. // SetDebug disables/enables debug level for logging component globally.
func SetDebug(debug bool) { func SetDebug(debug bool) {
glog.SetDebug(debug) glog.SetDebug(debug)
} }

View File

@ -47,7 +47,8 @@ func Database(name ...string) gdb.DB {
return gins.Database(name...) return gins.Database(name...)
} }
// DB is alias of Database. See Database. // DB is alias of Database.
// See Database.
func DB(name ...string) gdb.DB { func DB(name ...string) gdb.DB {
return gins.Database(name...) return gins.Database(name...)
} }

View File

@ -8,7 +8,7 @@ package g
import "github.com/gogf/gf/g/net/ghttp" import "github.com/gogf/gf/g/net/ghttp"
// SetServerGraceful enables/disables graceful/hot reload feature of http Web Server. // SetServerGraceful enables/disables graceful reload feature of http Web Server.
// This feature is disabled in default. // This feature is disabled in default.
func SetServerGraceful(enabled bool) { func SetServerGraceful(enabled bool) {
ghttp.SetGraceful(enabled) ghttp.SetGraceful(enabled)

View File

@ -9,9 +9,10 @@
package cmdenv package cmdenv
import ( import (
"github.com/gogf/gf/g/test/gtest"
"os" "os"
"testing" "testing"
"github.com/gogf/gf/g/test/gtest"
) )
func Test_Get(t *testing.T) { func Test_Get(t *testing.T) {

View File

@ -0,0 +1,48 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package errors provides simple functions to manipulate errors.
//
// This package can be scalable due to https://go.googlesource.com/proposal/+/master/design/go2draft.md.
package errors
import "github.com/gogf/gf/g/util/gconv"
// errorWrapper is a simple wrapper for errors.
type errorWrapper struct {
s string
}
// New returns an error that formats as the given value.
func New(value interface{}) error {
if value == nil {
return nil
}
return NewText(gconv.String(value))
}
// NewText returns an error that formats as the given text.
func NewText(text string) error {
if text == "" {
return nil
}
return &errorWrapper{
s: text,
}
}
// Wrap wraps error with text.
func Wrap(err error, text string) error {
if err == nil {
return nil
}
return NewText(text + ": " + err.Error())
}
// Error implements interface Error.
func (e *errorWrapper) Error() string {
return e.s
}

View File

@ -0,0 +1,39 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package errors_test
import (
"testing"
"github.com/gogf/gf/g/internal/errors"
"github.com/gogf/gf/g/test/gtest"
)
func interfaceNil() interface{} {
return nil
}
func nilError() error {
return nil
}
func Test_Nil(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(errors.New(interfaceNil()), nil)
gtest.Assert(errors.Wrap(nilError(), "test"), nil)
})
}
func Test_Wrap(t *testing.T) {
gtest.Case(t, func() {
err := errors.New("1")
err = errors.Wrap(err, "func2 error")
err = errors.Wrap(err, "func3 error")
gtest.AssertNE(err, nil)
gtest.Assert(err.Error(), "func3 error: func2 error: 1")
})
}

View File

@ -8,7 +8,6 @@
package ghttp package ghttp
import ( import (
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/util/gconv"
) )
@ -26,7 +25,7 @@ type CORSOptions struct {
// 默认的CORS配置 // 默认的CORS配置
func (r *Response) DefaultCORSOptions() CORSOptions { func (r *Response) DefaultCORSOptions() CORSOptions {
return CORSOptions{ return CORSOptions{
AllowOrigin: gstr.TrimRight(r.request.Referer(), "/"), AllowOrigin: "*",
AllowMethods: HTTP_METHODS, AllowMethods: HTTP_METHODS,
AllowCredentials: "true", AllowCredentials: "true",
MaxAge: 3628800, MaxAge: 3628800,

View File

@ -10,6 +10,14 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"net/http"
"os"
"reflect"
"runtime"
"strings"
"sync"
"time"
"github.com/gogf/gf/g/container/garray" "github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gtype"
@ -23,13 +31,6 @@ import (
"github.com/gogf/gf/g/util/gconv" "github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/third/github.com/gorilla/websocket" "github.com/gogf/gf/third/github.com/gorilla/websocket"
"github.com/gogf/gf/third/github.com/olekukonko/tablewriter" "github.com/gogf/gf/third/github.com/olekukonko/tablewriter"
"net/http"
"os"
"reflect"
"runtime"
"strings"
"sync"
"time"
) )
type ( type (
@ -382,15 +383,14 @@ func (s *Server) GetRouteMap() string {
} }
// 阻塞执行监听 // 阻塞执行监听
func (s *Server) Run() error { func (s *Server) Run() {
if err := s.Start(); err != nil { if err := s.Start(); err != nil {
return err glog.Fatal(err)
} }
// 阻塞等待服务执行完成 // 阻塞等待服务执行完成
<-s.closeChan <-s.closeChan
glog.Printf("%d: all servers shutdown", gproc.Pid()) glog.Printf("%d: all servers shutdown", gproc.Pid())
return nil
} }
// 阻塞等待所有Web Server停止常用于多Web Server场景以及需要将Web Server异步运行的场景 // 阻塞等待所有Web Server停止常用于多Web Server场景以及需要将Web Server异步运行的场景

View File

@ -14,6 +14,8 @@ import (
"io" "io"
"net" "net"
"time" "time"
"github.com/gogf/gf/g/internal/errors"
) )
// 封装的链接对象 // 封装的链接对象
@ -208,9 +210,7 @@ func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry
return nil, err return nil, err
} }
defer func() { defer func() {
if e := c.SetRecvDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
data, err = c.Recv(length, retry...) data, err = c.Recv(length, retry...)
return return
@ -222,9 +222,7 @@ func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retr
return err return err
} }
defer func() { defer func() {
if e := c.SetSendDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
err = c.Send(data, retry...) err = c.Send(data, retry...)
return return

View File

@ -8,9 +8,10 @@ package gtcp
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"time" "time"
"github.com/gogf/gf/g/internal/errors"
) )
const ( const (
@ -73,9 +74,7 @@ func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...
return err return err
} }
defer func() { defer func() {
if e := c.SetSendDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
err = c.SendPkg(data, option...) err = c.SendPkg(data, option...)
return return
@ -148,9 +147,7 @@ func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) (d
return nil, err return nil, err
} }
defer func() { defer func() {
if e := c.SetRecvDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
data, err = c.RecvPkg(option...) data, err = c.RecvPkg(option...)
return return

View File

@ -7,11 +7,10 @@
package gtcp package gtcp
import ( import (
"errors"
"time"
"github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/container/gpool" "github.com/gogf/gf/g/container/gpool"
"github.com/gogf/gf/g/internal/errors"
"time"
) )
// 链接池链接对象 // 链接池链接对象
@ -121,9 +120,7 @@ func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry ...R
return nil, err return nil, err
} }
defer func() { defer func() {
if e := c.SetRecvDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
data, err = c.Recv(length, retry...) data, err = c.Recv(length, retry...)
return return
@ -135,9 +132,7 @@ func (c *PoolConn) SendWithTimeout(data []byte, timeout time.Duration, retry ...
return err return err
} }
defer func() { defer func() {
if e := c.SetSendDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
err = c.Send(data, retry...) err = c.Send(data, retry...)
return return

View File

@ -9,6 +9,8 @@ package gtcp
import ( import (
"errors" "errors"
"time" "time"
"github.com/gogf/gf/g/internal/errors"
) )
// 简单协议: (方法覆盖)发送数据 // 简单协议: (方法覆盖)发送数据
@ -46,9 +48,7 @@ func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption
return nil, err return nil, err
} }
defer func() { defer func() {
if e := c.SetRecvDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
data, err = c.RecvPkg(option...) data, err = c.RecvPkg(option...)
return return
@ -60,9 +60,7 @@ func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option
return err return err
} }
defer func() { defer func() {
if e := c.SetSendDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
err = c.SendPkg(data, option...) err = c.SendPkg(data, option...)
return return

View File

@ -11,6 +11,8 @@ import (
"io" "io"
"net" "net"
"time" "time"
"github.com/gogf/gf/g/internal/errors"
) )
// 封装的UDP链接对象 // 封装的UDP链接对象
@ -181,9 +183,7 @@ func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry
return nil, err return nil, err
} }
defer func() { defer func() {
if e := c.SetRecvDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetRecvDeadline(time.Time{}), "SetRecvDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
data, err = c.Recv(length, retry...) data, err = c.Recv(length, retry...)
return return
@ -195,9 +195,7 @@ func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retr
return err return err
} }
defer func() { defer func() {
if e := c.SetSendDeadline(time.Time{}); e != nil { err = errors.Wrap(c.SetSendDeadline(time.Time{}), "SetSendDeadline error")
err = errors.New(err.Error() + "; " + e.Error())
}
}() }()
err = c.Send(data, retry...) err = c.Send(data, retry...)
return return

View File

@ -7,11 +7,12 @@
package gcron_test package gcron_test
import ( import (
"testing"
"time"
"github.com/gogf/gf/g/container/garray" "github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/os/gcron" "github.com/gogf/gf/g/os/gcron"
"github.com/gogf/gf/g/test/gtest" "github.com/gogf/gf/g/test/gtest"
"testing"
"time"
) )
func TestCron_Add_Close(t *testing.T) { func TestCron_Add_Close(t *testing.T) {
@ -146,7 +147,7 @@ func TestCron_AddOnce2(t *testing.T) {
array.Append(1) array.Append(1)
}) })
gtest.Assert(cron.Size(), 1) gtest.Assert(cron.Size(), 1)
time.Sleep(2500 * time.Millisecond) time.Sleep(3000 * time.Millisecond)
gtest.Assert(array.Len(), 1) gtest.Assert(array.Len(), 1)
gtest.Assert(cron.Size(), 0) gtest.Assert(cron.Size(), 0)
}) })

View File

@ -10,13 +10,11 @@ package gflock
import ( import (
"github.com/gogf/gf/g/os/gfile" "github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/third/github.com/theckman/go-flock" "github.com/gogf/gf/third/github.com/theckman/go-flock"
"sync"
) )
// File locker. // File locker.
type Locker struct { type Locker struct {
mu sync.RWMutex // 用于外部接口调用的互斥锁(阻塞机制) flock *flock.Flock // Underlying file locker.
flock *flock.Flock // 底层文件锁对象
} }
// New creates and returns a new file locker with given <file>. // New creates and returns a new file locker with given <file>.
@ -47,9 +45,6 @@ func (l *Locker) IsLocked() bool {
// It returns true if success, or else returns false immediately. // It returns true if success, or else returns false immediately.
func (l *Locker) TryLock() bool { func (l *Locker) TryLock() bool {
ok, _ := l.flock.TryLock() ok, _ := l.flock.TryLock()
if ok {
l.mu.Lock()
}
return ok return ok
} }
@ -57,32 +52,61 @@ func (l *Locker) TryLock() bool {
// It returns true if success, or else returns false immediately. // It returns true if success, or else returns false immediately.
func (l *Locker) TryRLock() bool { func (l *Locker) TryRLock() bool {
ok, _ := l.flock.TryRLock() ok, _ := l.flock.TryRLock()
if ok {
l.mu.RLock()
}
return ok return ok
} }
// Lock is a blocking call to try and take an exclusive file lock. It will wait
// until it is able to obtain the exclusive file lock. It's recommended that
// TryLock() be used over this function. This function may block the ability to
// query the current Locked() or RLocked() status due to a RW-mutex lock.
//
// If we are already exclusive-locked, this function short-circuits and returns
// immediately assuming it can take the mutex lock.
//
// If the *Flock has a shared lock (RLock), this may transparently replace the
// shared lock with an exclusive lock on some UNIX-like operating systems. Be
// careful when using exclusive locks in conjunction with shared locks
// (RLock()), because calling Unlock() may accidentally release the exclusive
// lock that was once a shared lock.
func (l *Locker) Lock() (err error) { func (l *Locker) Lock() (err error) {
l.mu.Lock() return l.flock.Lock()
err = l.flock.Lock()
return
} }
// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
// while it is running the Locked() and RLocked() functions will be blocked.
//
// This function short-circuits if we are unlocked already. If not, it calls
// syscall.LOCK_UN on the file and closes the file descriptor. It does not
// remove the file from disk. It's up to your application to do.
//
// Please note, if your shared lock became an exclusive lock this may
// unintentionally drop the exclusive lock if called by the consumer that
// believes they have a shared lock. Please see Lock() for more details.
func (l *Locker) UnLock() (err error) { func (l *Locker) UnLock() (err error) {
err = l.flock.Unlock() return l.flock.Unlock()
l.mu.Unlock()
return
} }
// RLock is a blocking call to try and take a ahred file lock. It will wait
// until it is able to obtain the shared file lock. It's recommended that
// TryRLock() be used over this function. This function may block the ability to
// query the current Locked() or RLocked() status due to a RW-mutex lock.
//
// If we are already shared-locked, this function short-circuits and returns
// immediately assuming it can take the mutex lock.
func (l *Locker) RLock() (err error) { func (l *Locker) RLock() (err error) {
l.mu.RLock() return l.flock.RLock()
err = l.flock.RLock()
return
} }
// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
// while it is running the Locked() and RLocked() functions will be blocked.
//
// This function short-circuits if we are unlocked already. If not, it calls
// syscall.LOCK_UN on the file and closes the file descriptor. It does not
// remove the file from disk. It's up to your application to do.
//
// Please note, if your shared lock became an exclusive lock this may
// unintentionally drop the exclusive lock if called by the consumer that
// believes they have a shared lock. Please see Lock() for more details.
func (l *Locker) RUnlock() (err error) { func (l *Locker) RUnlock() (err error) {
err = l.flock.Unlock() return l.flock.Unlock()
l.mu.RUnlock()
return
} }

View File

@ -7,26 +7,23 @@
// Package gmlock implements a concurrent-safe memory-based locker. // Package gmlock implements a concurrent-safe memory-based locker.
package gmlock package gmlock
import "time"
var ( var (
// Default locker. // Default locker.
locker = New() locker = New()
) )
// TryLock tries locking the <key> with writing lock,
// it returns true if success, or if there's a write/reading lock the <key>,
// it returns false. The parameter <expire> specifies the max duration it locks.
func TryLock(key string, expire ...time.Duration) bool {
return locker.TryLock(key, expire...)
}
// Lock locks the <key> with writing lock. // Lock locks the <key> with writing lock.
// If there's a write/reading lock the <key>, // If there's a write/reading lock the <key>,
// it will blocks until the lock is released. // it will blocks until the lock is released.
// The parameter <expire> specifies the max duration it locks. func Lock(key string) {
func Lock(key string, expire ...time.Duration) { locker.Lock(key)
locker.Lock(key, expire...) }
// TryLock tries locking the <key> with writing lock,
// it returns true if success, or if there's a write/reading lock the <key>,
// it returns false.
func TryLock(key string) bool {
return locker.TryLock(key)
} }
// Unlock unlocks the writing lock of the <key>. // Unlock unlocks the writing lock of the <key>.
@ -34,12 +31,6 @@ func Unlock(key string) {
locker.Unlock(key) locker.Unlock(key)
} }
// TryRLock tries locking the <key> with reading lock.
// It returns true if success, or if there's a writing lock on <key>, it returns false.
func TryRLock(key string) bool {
return locker.TryRLock(key)
}
// RLock locks the <key> with reading lock. // RLock locks the <key> with reading lock.
// If there's a writing lock on <key>, // If there's a writing lock on <key>,
// it will blocks until the writing lock is released. // it will blocks until the writing lock is released.
@ -47,40 +38,24 @@ func RLock(key string) {
locker.RLock(key) locker.RLock(key)
} }
// TryRLock tries locking the <key> with reading lock.
// It returns true if success, or if there's a writing lock on <key>, it returns false.
func TryRLock(key string) bool {
return locker.TryRLock(key)
}
// RUnlock unlocks the reading lock of the <key>. // RUnlock unlocks the reading lock of the <key>.
func RUnlock(key string) { func RUnlock(key string) {
locker.RUnlock(key) locker.RUnlock(key)
} }
// TryLockFunc locks the <key> with writing lock and callback function <f>.
// It returns true if success, or else if there's a write/reading lock the <key>, it return false.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func TryLockFunc(key string, f func(), expire ...time.Duration) bool {
return locker.TryLockFunc(key, f, expire...)
}
// TryRLockFunc locks the <key> with reading lock and callback function <f>.
// It returns true if success, or else if there's a writing lock the <key>, it returns false.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func TryRLockFunc(key string, f func()) bool {
return locker.TryRLockFunc(key, f)
}
// LockFunc locks the <key> with writing lock and callback function <f>. // LockFunc locks the <key> with writing lock and callback function <f>.
// If there's a write/reading lock the <key>, // If there's a write/reading lock the <key>,
// it will blocks until the lock is released. // it will blocks until the lock is released.
// //
// It releases the lock after <f> is executed. // It releases the lock after <f> is executed.
// func LockFunc(key string, f func()) {
// The parameter <expire> specifies the max duration it locks. locker.LockFunc(key, f)
func LockFunc(key string, f func(), expire ...time.Duration) {
locker.LockFunc(key, f, expire...)
} }
// RLockFunc locks the <key> with reading lock and callback function <f>. // RLockFunc locks the <key> with reading lock and callback function <f>.
@ -88,8 +63,27 @@ func LockFunc(key string, f func(), expire ...time.Duration) {
// it will blocks until the lock is released. // it will blocks until the lock is released.
// //
// It releases the lock after <f> is executed. // It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func RLockFunc(key string, f func()) { func RLockFunc(key string, f func()) {
locker.RLockFunc(key, f) locker.RLockFunc(key, f)
} }
// TryLockFunc locks the <key> with writing lock and callback function <f>.
// It returns true if success, or else if there's a write/reading lock the <key>, it return false.
//
// It releases the lock after <f> is executed.
func TryLockFunc(key string, f func()) bool {
return locker.TryLockFunc(key, f)
}
// TryRLockFunc locks the <key> with reading lock and callback function <f>.
// It returns true if success, or else if there's a writing lock the <key>, it returns false.
//
// It releases the lock after <f> is executed.
func TryRLockFunc(key string, f func()) bool {
return locker.TryRLockFunc(key, f)
}
// Remove removes mutex with given <key>.
func Remove(key string) {
locker.Remove(key)
}

View File

@ -8,11 +8,12 @@ package gmlock
import ( import (
"github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/os/gtimer" "github.com/gogf/gf/g/os/gmutex"
"time"
) )
// Memory locker. // Memory locker.
// Note that there's no cache expire mechanism for mutex in locker.
// You need remove certain mutex manually when you do not want use it any more.
type Locker struct { type Locker struct {
m *gmap.StrAnyMap m *gmap.StrAnyMap
} }
@ -25,56 +26,74 @@ func New() *Locker {
} }
} }
// TryLock tries locking the <key> with writing lock,
// it returns true if success, or it returns false if there's a writing/reading lock the <key>.
// The parameter <expire> specifies the max duration it locks.
func (l *Locker) TryLock(key string, expire ...time.Duration) bool {
return l.doLock(key, l.getExpire(expire...), true)
}
// Lock locks the <key> with writing lock. // Lock locks the <key> with writing lock.
// If there's a write/reading lock the <key>, // If there's a write/reading lock the <key>,
// it will blocks until the lock is released. // it will blocks until the lock is released.
// The parameter <expire> specifies the max duration it locks. func (l *Locker) Lock(key string) {
func (l *Locker) Lock(key string, expire ...time.Duration) { l.getOrNewMutex(key).Lock()
l.doLock(key, l.getExpire(expire...), false) }
// TryLock tries locking the <key> with writing lock,
// it returns true if success, or it returns false if there's a writing/reading lock the <key>.
func (l *Locker) TryLock(key string) bool {
return l.getOrNewMutex(key).TryLock()
} }
// Unlock unlocks the writing lock of the <key>. // Unlock unlocks the writing lock of the <key>.
func (l *Locker) Unlock(key string) { func (l *Locker) Unlock(key string) {
if v := l.m.Get(key); v != nil { if v := l.m.Get(key); v != nil {
v.(*Mutex).Unlock() v.(*gmutex.Mutex).Unlock()
} }
} }
// TryRLock tries locking the <key> with reading lock.
// It returns true if success, or if there's a writing lock on <key>, it returns false.
func (l *Locker) TryRLock(key string) bool {
return l.doRLock(key, true)
}
// RLock locks the <key> with reading lock. // RLock locks the <key> with reading lock.
// If there's a writing lock on <key>, // If there's a writing lock on <key>,
// it will blocks until the writing lock is released. // it will blocks until the writing lock is released.
func (l *Locker) RLock(key string) { func (l *Locker) RLock(key string) {
l.doRLock(key, false) l.getOrNewMutex(key).RLock()
}
// TryRLock tries locking the <key> with reading lock.
// It returns true if success, or if there's a writing lock on <key>, it returns false.
func (l *Locker) TryRLock(key string) bool {
return l.getOrNewMutex(key).TryRLock()
} }
// RUnlock unlocks the reading lock of the <key>. // RUnlock unlocks the reading lock of the <key>.
func (l *Locker) RUnlock(key string) { func (l *Locker) RUnlock(key string) {
if v := l.m.Get(key); v != nil { if v := l.m.Get(key); v != nil {
v.(*Mutex).RUnlock() v.(*gmutex.Mutex).RUnlock()
} }
} }
// LockFunc locks the <key> with writing lock and callback function <f>.
// If there's a write/reading lock the <key>,
// it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
func (l *Locker) LockFunc(key string, f func()) {
l.Lock(key)
defer l.Unlock(key)
f()
}
// RLockFunc locks the <key> with reading lock and callback function <f>.
// If there's a writing lock the <key>,
// it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
func (l *Locker) RLockFunc(key string, f func()) {
l.RLock(key)
defer l.RUnlock(key)
f()
}
// TryLockFunc locks the <key> with writing lock and callback function <f>. // TryLockFunc locks the <key> with writing lock and callback function <f>.
// It returns true if success, or else if there's a write/reading lock the <key>, it return false. // It returns true if success, or else if there's a write/reading lock the <key>, it return false.
// //
// It releases the lock after <f> is executed. // It releases the lock after <f> is executed.
// func (l *Locker) TryLockFunc(key string, f func()) bool {
// The parameter <expire> specifies the max duration it locks. if l.TryLock(key) {
func (l *Locker) TryLockFunc(key string, f func(), expire ...time.Duration) bool {
if l.TryLock(key, expire...) {
defer l.Unlock(key) defer l.Unlock(key)
f() f()
return true return true
@ -86,8 +105,6 @@ func (l *Locker) TryLockFunc(key string, f func(), expire ...time.Duration) bool
// It returns true if success, or else if there's a writing lock the <key>, it returns false. // It returns true if success, or else if there's a writing lock the <key>, it returns false.
// //
// It releases the lock after <f> is executed. // It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func (l *Locker) TryRLockFunc(key string, f func()) bool { func (l *Locker) TryRLockFunc(key string, f func()) bool {
if l.TryRLock(key) { if l.TryRLock(key) {
defer l.RUnlock(key) defer l.RUnlock(key)
@ -97,90 +114,20 @@ func (l *Locker) TryRLockFunc(key string, f func()) bool {
return false return false
} }
// LockFunc locks the <key> with writing lock and callback function <f>. // Remove removes mutex with given <key> from locker.
// If there's a write/reading lock the <key>, func (l *Locker) Remove(key string) {
// it will blocks until the lock is released. l.m.Remove(key)
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func (l *Locker) LockFunc(key string, f func(), expire ...time.Duration) {
l.Lock(key, expire...)
defer l.Unlock(key)
f()
} }
// RLockFunc locks the <key> with reading lock and callback function <f>. // Clear removes all mutexes from locker.
// If there's a writing lock the <key>, func (l *Locker) Clear() {
// it will blocks until the lock is released. l.m.Clear()
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func (l *Locker) RLockFunc(key string, f func()) {
l.RLock(key)
defer l.RUnlock(key)
f()
}
// getExpire returns the duration object passed.
// If <expire> is not passed, it returns a default duration object.
func (l *Locker) getExpire(expire ...time.Duration) time.Duration {
e := time.Duration(0)
if len(expire) > 0 {
e = expire[0]
}
return e
}
// doLock locks writing on <key>.
// It returns true if success, or else returns false.
//
// The parameter <try> is true,
// it returns false immediately if it fails getting the writing lock.
// If <true> is false, it blocks until it gets the writing lock.
//
// The parameter <expire> specifies the max duration it locks.
func (l *Locker) doLock(key string, expire time.Duration, try bool) bool {
mu := l.getOrNewMutex(key)
ok := true
if try {
ok = mu.TryLock()
} else {
mu.Lock()
}
if ok && expire > 0 {
wid := mu.wid.Val()
gtimer.AddOnce(expire, func() {
if wid == mu.wid.Val() {
mu.Unlock()
}
})
}
return ok
}
// doRLock locks reading on <key>.
// It returns true if success, or else returns false.
//
// The parameter <try> is true,
// it returns false immediately if it fails getting the reading lock.
// If <true> is false, it blocks until it gets the reading lock.
func (l *Locker) doRLock(key string, try bool) bool {
mu := l.getOrNewMutex(key)
ok := true
if try {
ok = mu.TryRLock()
} else {
mu.RLock()
}
return ok
} }
// getOrNewMutex returns the mutex of given <key> if it exists, // getOrNewMutex returns the mutex of given <key> if it exists,
// or else creates and returns a new one. // or else creates and returns a new one.
func (l *Locker) getOrNewMutex(key string) *Mutex { func (l *Locker) getOrNewMutex(key string) *gmutex.Mutex {
return l.m.GetOrSetFuncLock(key, func() interface{} { return l.m.GetOrSetFuncLock(key, func() interface{} {
return NewMutex() return gmutex.New()
}).(*Mutex) }).(*gmutex.Mutex)
} }

View File

@ -44,58 +44,61 @@ func Test_Locker_Lock(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 4) gtest.Assert(array.Len(), 4)
}) })
//expire }
func Test_Locker_LockFunc(t *testing.T) {
//no expire
gtest.Case(t, func() { gtest.Case(t, func() {
key := "testLockExpire" key := "testLockFunc"
array := garray.New() array := garray.New()
go func() { go func() {
gmlock.Lock(key, 100*time.Millisecond) gmlock.LockFunc(key, func() {
array.Append(1) array.Append(1)
time.Sleep(50 * time.Millisecond)
}) //
}() }()
go func() { go func() {
time.Sleep(10 * time.Millisecond) time.Sleep(10 * time.Millisecond)
gmlock.Lock(key) gmlock.LockFunc(key, func() {
time.Sleep(100 * time.Millisecond) array.Append(1)
array.Append(1) })
gmlock.Unlock(key)
}() }()
time.Sleep(150 * time.Millisecond) time.Sleep(10 * time.Millisecond)
gtest.Assert(array.Len(), 1) gtest.Assert(array.Len(), 1)
time.Sleep(250 * time.Millisecond) time.Sleep(20 * time.Millisecond)
gtest.Assert(array.Len(), 1) //
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 2) gtest.Assert(array.Len(), 2)
}) })
} }
func Test_Locker_TryLock(t *testing.T) { func Test_Locker_TryLockFunc(t *testing.T) {
//no expire
gtest.Case(t, func() { gtest.Case(t, func() {
key := "testTryLock" key := "testTryLockFunc"
array := garray.New() array := garray.New()
go func() { go func() {
if gmlock.TryLock(key, 200*time.Millisecond) { gmlock.TryLockFunc(key, func() {
array.Append(1) array.Append(1)
} time.Sleep(50 * time.Millisecond)
})
}() }()
go func() { go func() {
time.Sleep(100 * time.Millisecond) time.Sleep(10 * time.Millisecond)
if !gmlock.TryLock(key) { gmlock.TryLockFunc(key, func() {
array.Append(1) array.Append(1)
} else { })
gmlock.Unlock(key)
}
}() }()
go func() { go func() {
time.Sleep(300 * time.Millisecond) time.Sleep(70 * time.Millisecond)
if gmlock.TryLock(key) { gmlock.TryLockFunc(key, func() {
array.Append(1) array.Append(1)
gmlock.Unlock(key) })
}
}() }()
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 1) gtest.Assert(array.Len(), 1)
time.Sleep(80 * time.Millisecond) time.Sleep(100 * time.Millisecond)
gtest.Assert(array.Len(), 2) gtest.Assert(array.Len(), 2)
time.Sleep(350 * time.Millisecond)
gtest.Assert(array.Len(), 3)
}) })
} }

View File

@ -147,7 +147,7 @@ func Test_Locker_TryRLock(t *testing.T) {
}) })
} }
func Test_Locker_RLockFunc(t *testing.T) { func Test_Locker_RLockFunc1(t *testing.T) {
//RLockFunc before Lock //RLockFunc before Lock
gtest.Case(t, func() { gtest.Case(t, func() {
key := "testRLockFuncBeforeLock" key := "testRLockFuncBeforeLock"
@ -155,7 +155,7 @@ func Test_Locker_RLockFunc(t *testing.T) {
go func() { go func() {
gmlock.RLockFunc(key, func() { gmlock.RLockFunc(key, func() {
array.Append(1) array.Append(1)
time.Sleep(50 * time.Millisecond) time.Sleep(500 * time.Millisecond)
array.Append(1) array.Append(1)
}) })
}() }()
@ -165,9 +165,10 @@ func Test_Locker_RLockFunc(t *testing.T) {
array.Append(1) array.Append(1)
gmlock.Unlock(key) gmlock.Unlock(key)
}() }()
time.Sleep(20 * time.Millisecond)
time.Sleep(200 * time.Millisecond)
gtest.Assert(array.Len(), 1) gtest.Assert(array.Len(), 1)
time.Sleep(80 * time.Millisecond) time.Sleep(800 * time.Millisecond)
gtest.Assert(array.Len(), 3) gtest.Assert(array.Len(), 3)
}) })
@ -193,6 +194,10 @@ func Test_Locker_RLockFunc(t *testing.T) {
gtest.Assert(array.Len(), 2) gtest.Assert(array.Len(), 2)
}) })
}
func Test_Locker_RLockFunc2(t *testing.T) {
//Lock before RLockFuncs //Lock before RLockFuncs
gtest.Case(t, func() { gtest.Case(t, func() {
key := "testLockBeforeRLockFuncs" key := "testLockBeforeRLockFuncs"
@ -200,26 +205,29 @@ func Test_Locker_RLockFunc(t *testing.T) {
go func() { go func() {
gmlock.Lock(key) gmlock.Lock(key)
array.Append(1) array.Append(1)
time.Sleep(50 * time.Millisecond) //glog.Println("add1")
time.Sleep(500 * time.Millisecond)
gmlock.Unlock(key) gmlock.Unlock(key)
}() }()
go func() { go func() {
time.Sleep(10 * time.Millisecond) time.Sleep(100 * time.Millisecond)
gmlock.RLockFunc(key, func() { gmlock.RLockFunc(key, func() {
array.Append(1) array.Append(1)
time.Sleep(70 * time.Millisecond) //glog.Println("add2")
time.Sleep(700 * time.Millisecond)
}) })
}() }()
go func() { go func() {
time.Sleep(10 * time.Millisecond) time.Sleep(100 * time.Millisecond)
gmlock.RLockFunc(key, func() { gmlock.RLockFunc(key, func() {
array.Append(1) array.Append(1)
time.Sleep(70 * time.Millisecond) //glog.Println("add3")
time.Sleep(700 * time.Millisecond)
}) })
}() }()
time.Sleep(20 * time.Millisecond) time.Sleep(200 * time.Millisecond)
gtest.Assert(array.Len(), 1) gtest.Assert(array.Len(), 1)
time.Sleep(70 * time.Millisecond) time.Sleep(700 * time.Millisecond)
gtest.Assert(array.Len(), 3) gtest.Assert(array.Len(), 3)
}) })
} }

View File

@ -1,37 +1,35 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved. // Copyright 2018-2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// //
// This Source Code Form is subject to the terms of the MIT License. // This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file, // If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf. // You can obtain one at https://github.com/gogf/gf.
package gmlock // Package gmutex implements graceful concurrent-safe mutex with more rich features.
package gmutex
import ( import (
"runtime"
"sync"
"github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gtype"
"math"
"runtime"
) )
// The high level RWMutex. // The high level Mutex, which implements more rich features for mutex.
// It wraps the sync.RWMutex to implements more rich features.
type Mutex struct { type Mutex struct {
mu sync.RWMutex state *gtype.Int32 // Indicates the state of mutex.
wid *gtype.Int64 // Unique id, used for multiple and safe logic Unlock. writer *gtype.Int32 // Pending writer count.
locking *gtype.Bool // Locking mark for atomic operation for *Lock and Try*Lock functions. reader *gtype.Int32 // Pending reader count.
// There must be only one locking operation at the same time for concurrent safe purpose. writing chan struct{} // Channel used for writer blocking.
state *gtype.Int32 // Locking state: reading chan struct{} // Channel used for reader blocking.
// 0: writing lock false;
// -1: writing lock true;
// >=1: reading lock;
} }
// NewMutex creates and returns a new mutex. // New creates and returns a new mutex.
func NewMutex() *Mutex { func New() *Mutex {
return &Mutex{ return &Mutex{
wid: gtype.NewInt64(),
state: gtype.NewInt32(), state: gtype.NewInt32(),
locking: gtype.NewBool(), writer: gtype.NewInt32(),
reader: gtype.NewInt32(),
writing: make(chan struct{}, 1),
reading: make(chan struct{}, math.MaxInt32),
} }
} }
@ -39,15 +37,15 @@ func NewMutex() *Mutex {
// If the lock is already locked for reading or writing, // If the lock is already locked for reading or writing,
// Lock blocks until the lock is available. // Lock blocks until the lock is available.
func (m *Mutex) Lock() { func (m *Mutex) Lock() {
if m.locking.Cas(false, true) { for {
m.mu.Lock() // If there're no readers pending and writing lock currently,
// State should be changed after locks. // then do the writing lock checks.
m.state.Set(-1) if m.reader.Val() == 0 && m.state.Cas(0, -1) {
m.wid.Add(1) return
m.locking.Set(false) }
} else { m.writer.Add(1)
runtime.Gosched() <-m.writing
m.Lock() m.writer.Add(-1)
} }
} }
@ -55,7 +53,17 @@ func (m *Mutex) Lock() {
// It is safe to be called multiple times if there's any locks or not. // It is safe to be called multiple times if there's any locks or not.
func (m *Mutex) Unlock() { func (m *Mutex) Unlock() {
if m.state.Cas(-1, 0) { if m.state.Cas(-1, 0) {
m.mu.Unlock() // Writing lock unlocks, then first check the blocked readers.
if n := m.reader.Val(); n > 0 {
// If there're readers blocked, unlock them with preemption.
for ; n > 0; n-- {
m.reading <- struct{}{}
}
return
}
if m.writer.Val() > 0 {
m.writing <- struct{}{}
}
} }
} }
@ -63,14 +71,8 @@ func (m *Mutex) Unlock() {
// It returns true if success, or if there's a write/reading lock on the mutex, // It returns true if success, or if there's a write/reading lock on the mutex,
// it returns false. // it returns false.
func (m *Mutex) TryLock() bool { func (m *Mutex) TryLock() bool {
if m.locking.Cas(false, true) { if m.reader.Val() == 0 && m.state.Cas(0, -1) {
if m.state.Cas(0, -1) { return true
m.mu.Lock()
m.wid.Add(1)
m.locking.Set(false)
return true
}
m.locking.Set(false)
} }
return false return false
} }
@ -79,59 +81,106 @@ func (m *Mutex) TryLock() bool {
// If the mutex is already locked for writing, // If the mutex is already locked for writing,
// It blocks until the lock is available. // It blocks until the lock is available.
func (m *Mutex) RLock() { func (m *Mutex) RLock() {
if m.locking.Cas(false, true) { var n int32
m.mu.RLock() for {
// State should be changed after locks. // If there're no writing lock and pending writers currently,
m.state.Add(1) // then do the reading lock checks.
m.locking.Set(false) if n = m.state.Val(); n >= 0 && m.writer.Val() == 0 {
} else { if m.state.Cas(n, n+1) {
runtime.Gosched() return
m.RLock() } else {
runtime.Gosched()
continue
}
}
// Or else pending the reader.
m.reader.Add(1)
<-m.reading
m.reader.Add(-1)
} }
} }
// RUnlock unlocks the reading lock. // RUnlock unlocks the reading lock.
// It is safe to be called multiple times if there's any locks or not. // It is safe to be called multiple times if there's any locks or not.
func (m *Mutex) RUnlock() { func (m *Mutex) RUnlock() {
if n := m.state.Val(); n >= 1 { var n int32
if m.state.Cas(n, n-1) { for {
m.mu.RUnlock() if n = m.state.Val(); n >= 1 {
if m.state.Cas(n, n-1) {
break
} else {
runtime.Gosched()
}
} else { } else {
m.RUnlock() break
} }
} }
// Reading lock unlocks, then first check the blocked writers.
if n == 1 && m.writer.Val() > 0 {
// No readers blocked, then the writers can take place.
m.writing <- struct{}{}
}
} }
// TryRLock tries locking the mutex for reading. // TryRLock tries locking the mutex for reading.
// It returns true if success, or if there's a writing lock on the mutex, it returns false. // It returns true if success, or if there's a writing lock on the mutex, it returns false.
func (m *Mutex) TryRLock() bool { func (m *Mutex) TryRLock() bool {
if m.locking.Cas(false, true) { var n int32
if m.state.Val() >= 0 { for {
m.mu.RLock() if n = m.state.Val(); n >= 0 && m.writer.Val() == 0 {
m.state.Add(1) if m.state.Cas(n, n+1) {
m.locking.Set(false) return true
return true } else {
runtime.Gosched()
}
} else {
return false
} }
m.locking.Set(false) m.locking.Set(false)
} }
return false
} }
// IsLocked checks whether the mutex is locked by writing or reading lock. // IsLocked checks whether the mutex is locked with writing or reading lock.
// Note that the result might be changed after it's called,
// so it cannot be the criterion for atomic operations.
func (m *Mutex) IsLocked() bool { func (m *Mutex) IsLocked() bool {
return m.state.Val() != 0 return m.state.Val() != 0
} }
// IsRLocked checks whether the mutex is locked by writing lock. // IsWLocked checks whether the mutex is locked by writing lock.
// Note that the result might be changed after it's called,
// so it cannot be the criterion for atomic operations.
func (m *Mutex) IsWLocked() bool { func (m *Mutex) IsWLocked() bool {
return m.state.Val() < 0 return m.state.Val() < 0
} }
// IsRLocked checks whether the mutex is locked by reading lock. // IsRLocked checks whether the mutex is locked by reading lock.
// Note that the result might be changed after it's called,
// so it cannot be the criterion for atomic operations.
func (m *Mutex) IsRLocked() bool { func (m *Mutex) IsRLocked() bool {
return m.state.Val() > 0 return m.state.Val() > 0
} }
// LockFunc locks the mutex for writing with given callback function <f>.
// If there's a write/reading lock the mutex, it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
func (m *Mutex) LockFunc(f func()) {
m.Lock()
defer m.Unlock()
f()
}
// RLockFunc locks the mutex for reading with given callback function <f>.
// If there's a writing lock the mutex, it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
func (m *Mutex) RLockFunc(f func()) {
m.RLock()
defer m.RUnlock()
f()
}
// TryLockFunc tries locking the mutex for writing with given callback function <f>. // TryLockFunc tries locking the mutex for writing with given callback function <f>.
// it returns true if success, or if there's a write/reading lock on the mutex, // it returns true if success, or if there's a write/reading lock on the mutex,
// it returns false. // it returns false.
@ -158,23 +207,3 @@ func (m *Mutex) TryRLockFunc(f func()) bool {
} }
return false return false
} }
// LockFunc locks the mutex for writing with given callback function <f>.
// If there's a write/reading lock the mutex, it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
func (m *Mutex) LockFunc(f func()) {
m.Lock()
defer m.Unlock()
f()
}
// RLockFunc locks the mutex for reading with given callback function <f>.
// If there's a writing lock the mutex, it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
func (m *Mutex) RLockFunc(f func()) {
m.RLock()
defer m.RUnlock()
f()
}

View File

@ -0,0 +1,67 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmutex_test
import (
"sync"
"testing"
"github.com/gogf/gf/g/os/gmutex"
)
var (
mu = sync.Mutex{}
rwmu = sync.RWMutex{}
gmu = gmutex.New()
)
func Benchmark_Mutex_LockUnlock(b *testing.B) {
for i := 0; i < b.N; i++ {
mu.Lock()
mu.Unlock()
}
}
func Benchmark_RWMutex_LockUnlock(b *testing.B) {
for i := 0; i < b.N; i++ {
rwmu.Lock()
rwmu.Unlock()
}
}
func Benchmark_RWMutex_RLockRUnlock(b *testing.B) {
for i := 0; i < b.N; i++ {
rwmu.RLock()
rwmu.RUnlock()
}
}
func Benchmark_GMutex_LockUnlock(b *testing.B) {
for i := 0; i < b.N; i++ {
gmu.Lock()
gmu.Unlock()
}
}
func Benchmark_GMutex_TryLock(b *testing.B) {
for i := 0; i < b.N; i++ {
gmu.TryLock()
}
}
func Benchmark_GMutex_RLockRUnlock(b *testing.B) {
for i := 0; i < b.N; i++ {
gmu.RLock()
gmu.RUnlock()
}
}
func Benchmark_GMutex_TryRLock(b *testing.B) {
for i := 0; i < b.N; i++ {
gmu.TryRLock()
}
}

View File

@ -0,0 +1,258 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmutex_test
import (
"testing"
"time"
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/os/gmutex"
"github.com/gogf/gf/g/test/gtest"
)
func Test_Mutex_RUnlock(t *testing.T) {
gtest.Case(t, func() {
mu := gmutex.New()
for index := 0; index < 1000; index++ {
go func() {
mu.RLockFunc(func() {
time.Sleep(100 * time.Millisecond)
})
}()
}
time.Sleep(10 * time.Millisecond)
gtest.Assert(mu.IsRLocked(), true)
gtest.Assert(mu.IsLocked(), true)
gtest.Assert(mu.IsWLocked(), false)
for index := 0; index < 1000; index++ {
go func() {
mu.RUnlock()
}()
}
time.Sleep(150 * time.Millisecond)
gtest.Assert(mu.IsRLocked(), false)
})
}
func Test_Mutex_IsLocked(t *testing.T) {
gtest.Case(t, func() {
mu := gmutex.New()
go func() {
mu.LockFunc(func() {
time.Sleep(100 * time.Millisecond)
})
}()
time.Sleep(10 * time.Millisecond)
gtest.Assert(mu.IsLocked(), true)
gtest.Assert(mu.IsWLocked(), true)
gtest.Assert(mu.IsRLocked(), false)
time.Sleep(110 * time.Millisecond)
gtest.Assert(mu.IsLocked(), false)
gtest.Assert(mu.IsWLocked(), false)
go func() {
mu.RLockFunc(func() {
time.Sleep(100 * time.Millisecond)
})
}()
time.Sleep(10 * time.Millisecond)
gtest.Assert(mu.IsRLocked(), true)
gtest.Assert(mu.IsLocked(), true)
gtest.Assert(mu.IsWLocked(), false)
time.Sleep(110 * time.Millisecond)
gtest.Assert(mu.IsRLocked(), false)
})
}
func Test_Mutex_Unlock(t *testing.T) {
gtest.Case(t, func() {
mu := gmutex.New()
array := garray.New()
go func() {
mu.LockFunc(func() {
array.Append(1)
time.Sleep(100 * time.Millisecond)
})
}()
go func() {
time.Sleep(50 * time.Millisecond)
mu.LockFunc(func() {
array.Append(1)
})
}()
go func() {
time.Sleep(50 * time.Millisecond)
mu.LockFunc(func() {
array.Append(1)
})
}()
go func() {
time.Sleep(60 * time.Millisecond)
mu.Unlock()
mu.Unlock()
mu.Unlock()
}()
time.Sleep(20 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 3)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 3)
})
}
func Test_Mutex_LockFunc(t *testing.T) {
gtest.Case(t, func() {
mu := gmutex.New()
array := garray.New()
go func() {
mu.LockFunc(func() {
array.Append(1)
time.Sleep(100 * time.Millisecond)
})
}()
go func() {
time.Sleep(50 * time.Millisecond)
mu.LockFunc(func() {
array.Append(1)
})
}()
time.Sleep(20 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 2)
})
}
func Test_Mutex_TryLockFunc(t *testing.T) {
gtest.Case(t, func() {
mu := gmutex.New()
array := garray.New()
go func() {
mu.LockFunc(func() {
array.Append(1)
time.Sleep(100 * time.Millisecond)
})
}()
go func() {
time.Sleep(50 * time.Millisecond)
mu.TryLockFunc(func() {
array.Append(1)
})
}()
go func() {
time.Sleep(110 * time.Millisecond)
mu.TryLockFunc(func() {
array.Append(1)
})
}()
time.Sleep(20 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 2)
})
}
func Test_Mutex_RLockFunc(t *testing.T) {
gtest.Case(t, func() {
mu := gmutex.New()
array := garray.New()
go func() {
mu.LockFunc(func() {
array.Append(1)
time.Sleep(100 * time.Millisecond)
})
}()
go func() {
time.Sleep(50 * time.Millisecond)
mu.RLockFunc(func() {
array.Append(1)
time.Sleep(100 * time.Millisecond)
})
}()
time.Sleep(20 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 2)
})
gtest.Case(t, func() {
mu := gmutex.New()
array := garray.New()
go func() {
time.Sleep(50 * time.Millisecond)
mu.RLockFunc(func() {
array.Append(1)
time.Sleep(100 * time.Millisecond)
})
}()
go func() {
time.Sleep(50 * time.Millisecond)
mu.RLockFunc(func() {
array.Append(1)
time.Sleep(100 * time.Millisecond)
})
}()
go func() {
time.Sleep(50 * time.Millisecond)
mu.RLockFunc(func() {
array.Append(1)
time.Sleep(100 * time.Millisecond)
})
}()
gtest.Assert(array.Len(), 0)
time.Sleep(80 * time.Millisecond)
gtest.Assert(array.Len(), 3)
})
}
func Test_Mutex_TryRLockFunc(t *testing.T) {
gtest.Case(t, func() {
mu := gmutex.New()
array := garray.New()
go func() {
mu.LockFunc(func() {
array.Append(1)
time.Sleep(500 * time.Millisecond)
})
}()
go func() {
time.Sleep(200 * time.Millisecond)
mu.TryRLockFunc(func() {
array.Append(1)
})
}()
go func() {
time.Sleep(700 * time.Millisecond)
mu.TryRLockFunc(func() {
array.Append(1)
})
}()
go func() {
time.Sleep(700 * time.Millisecond)
mu.TryRLockFunc(func() {
array.Append(1)
})
}()
time.Sleep(100 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(500 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(500 * time.Millisecond)
gtest.Assert(array.Len(), 3)
})
}

View File

@ -8,6 +8,8 @@
package grpool package grpool
import ( import (
"errors"
"github.com/gogf/gf/g/container/glist" "github.com/gogf/gf/g/container/glist"
"github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gtype"
) )
@ -41,35 +43,46 @@ func New(limit ...int) *Pool {
// Add pushes a new job to the pool using default goroutine pool. // Add pushes a new job to the pool using default goroutine pool.
// The job will be executed asynchronously. // The job will be executed asynchronously.
func Add(f func()) { func Add(f func()) error {
pool.Add(f) return pool.Add(f)
} }
// Size returns current goroutine count of default goroutine pool. // Size returns current goroutine count of default goroutine pool.
func Size() int { func Size() int {
return pool.count.Val() return pool.Size()
} }
// Jobs returns current job count of default goroutine pool. // Jobs returns current job count of default goroutine pool.
func Jobs() int { func Jobs() int {
return pool.list.Len() return pool.Jobs()
} }
// Add pushes a new job to the pool. // Add pushes a new job to the pool.
// The job will be executed asynchronously. // The job will be executed asynchronously.
func (p *Pool) Add(f func()) { func (p *Pool) Add(f func()) error {
for p.closed.Val() {
return errors.New("pool closed")
}
p.list.PushFront(f) p.list.PushFront(f)
// check whether to create a new goroutine or not. var n int
if p.count.Val() == p.limit { for {
return n = p.count.Val()
if p.limit != -1 && n >= p.limit {
return nil
}
if p.count.Cas(n, n+1) {
break
}
} }
// ensure atomicity.
if p.limit != -1 && p.count.Add(1) > p.limit {
p.count.Add(-1)
return
}
// fork a new goroutine to consume the job list.
p.fork() p.fork()
return nil
}
// Cap returns the capacity of the pool.
// This capacity is defined when pool is created.
// If it returns -1 means no limit.
func (p *Pool) Cap() int {
return p.limit
} }
// Size returns current goroutine count of the pool. // Size returns current goroutine count of the pool.
@ -97,6 +110,11 @@ func (p *Pool) fork() {
}() }()
} }
// IsClosed returns if pool is closed.
func (p *Pool) IsClosed() bool {
return p.closed.Val()
}
// Close closes the goroutine pool, which makes all goroutines exit. // Close closes the goroutine pool, which makes all goroutines exit.
func (p *Pool) Close() { func (p *Pool) Close() {
p.closed.Set(true) p.closed.Set(true)

View File

@ -7,12 +7,14 @@
package gtest_test package gtest_test
import ( import (
"github.com/gogf/gf/g/test/gtest"
"testing" "testing"
"github.com/gogf/gf/g/test/gtest"
) )
func TestCase(t *testing.T) { func TestCase(t *testing.T) {
gtest.Case(t, func() { gtest.Case(t, func() {
gtest.Assert(1, 1) gtest.Assert(1, 1)
gtest.AssertNE(1, 0)
}) })
} }

View File

@ -9,9 +9,10 @@
package gregex_test package gregex_test
import ( import (
"github.com/gogf/gf/g/text/gregex"
"regexp" "regexp"
"testing" "testing"
"github.com/gogf/gf/g/text/gregex"
) )
var pattern = `(\w+).+\-\-\s*(.+)` var pattern = `(\w+).+\-\-\s*(.+)`

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
package main
import (
"github.com/gogf/gf/g/net/ghttp"
"net/http"
)
func main() {
s := ghttp.GetServer()
s.BindHandler("/log/handler", func(r *ghttp.Request) {
r.Response.WriteStatus(http.StatusNotFound, "文件找不到了")
})
s.SetAccessLogEnabled(true)
s.SetErrorLogEnabled(true)
//s.SetLogHandler(func(r *ghttp.Request, error ...interface{}) {
// if len(error) > 0 {
// // 如果是错误日志
// fmt.Println("错误产生了:", error[0])
// }
// // 这里是请求日志
// fmt.Println("请求处理完成,请求地址:", r.URL.String(), "请求结果:", r.Response.Status)
//})
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,17 @@
package main
import (
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
s := ghttp.GetServer()
s.BindHandler("/log/error", func(r *ghttp.Request) {
if j := r.GetJson(); j != nil {
r.Response.Write(j.Get("test"))
}
})
s.SetErrorLogEnabled(true)
s.SetPort(8199)
s.Run()
}

View File

@ -2,8 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"github.com/gogf/gf/third/github.com/theckman/go-flock"
"time" "time"
"github.com/gogf/gf/third/github.com/theckman/go-flock"
) )
func main() { func main() {

View File

@ -1,17 +1,18 @@
package main package main
import ( import (
"time"
"github.com/gogf/gf/g/os/gflock" "github.com/gogf/gf/g/os/gflock"
"github.com/gogf/gf/g/os/glog" "github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gproc" "github.com/gogf/gf/g/os/gproc"
"time"
) )
func main() { func main() {
l := gflock.New("demo.lock") l := gflock.New("demo.lock")
l.Lock() l.Lock()
glog.Printf("locked by pid: %d", gproc.Pid()) glog.Printf("locked by pid: %d", gproc.Pid())
time.Sleep(3 * time.Second) time.Sleep(10 * time.Second)
l.UnLock() l.UnLock()
glog.Printf("unlocked by pid: %d", gproc.Pid()) glog.Printf("unlocked by pid: %d", gproc.Pid())
} }

View File

@ -1,53 +1,24 @@
package main package main
import ( import (
"fmt" "github.com/gogf/gf/g"
"time" "github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/os/gmlock"
"github.com/gogf/gf/g/test/gtest"
) )
func main() { type Order struct{}
mu := gmlock.NewMutex()
array := garray.New() func (order *Order) Get(r *ghttp.Request) {
go func() { r.Response.Write("GET")
mu.LockFunc(func() { }
array.Append(1)
time.Sleep(100 * time.Millisecond) func main() {
fmt.Println("====unlock") s := g.Server()
}) s.BindHookHandlerByMap("/api.v1/*any", map[string]ghttp.HandlerFunc{
}() "BeforeServe": func(r *ghttp.Request) {
go func() { r.Response.CORSDefault()
time.Sleep(50 * time.Millisecond) },
fmt.Println("tryRLock1") })
mu.TryRLockFunc(func() { s.BindObjectRest("/api.v1/{.struct}", new(Order))
array.Append(1) s.SetPort(8199)
fmt.Println("tryRLock1 success") s.Run()
})
}()
go func() {
time.Sleep(150 * time.Millisecond)
fmt.Println("tryRLock2")
mu.TryRLockFunc(func() {
array.Append(1)
fmt.Println("tryRLock2 success")
})
}()
go func() {
time.Sleep(150 * time.Millisecond)
fmt.Println("tryRLock3")
mu.TryRLockFunc(func() {
array.Append(1)
fmt.Println("tryRLock3 success")
})
}()
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(50 * time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(150 * time.Millisecond)
fmt.Println("====array len:", array.Len())
gtest.Assert(array.Len(), 3)
} }

View File

@ -1,14 +1,39 @@
package main package main
import ( import (
"fmt" "github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gmlock" "github.com/gogf/gf/g/net/ghttp"
"time"
) )
func main() { type Schedule struct{}
key := "test3"
gmlock.Lock(key, 200*time.Millisecond) type Task struct{}
fmt.Println("TryLock:", gmlock.TryLock(key))
fmt.Println("TryLock:", gmlock.TryLock(key)) func (c *Schedule) ListDir(r *ghttp.Request) {
r.Response.Writeln("ListDir")
}
func (c *Task) Add(r *ghttp.Request) {
r.Response.Writeln("Add")
}
func (c *Task) Task(r *ghttp.Request) {
r.Response.Writeln("Task")
}
// 实现权限校验
// 通过事件回调,类似于中间件机制,但是可控制的粒度更细,可以精准注册到路由规则
func AuthHookHandler(r *ghttp.Request) {
// 如果权限校验失败,调用 r.ExitAll() 退出执行流程
}
func main() {
s := g.Server()
s.Group("/schedule").Bind([]ghttp.GroupItem{
{"ALL", "*", AuthHookHandler, ghttp.HOOK_BEFORE_SERVE},
{"POST", "/schedule", new(Schedule)},
{"POST", "/task", new(Task)},
})
s.SetPort(8199)
s.Run()
} }

2
go.mod
View File

@ -1 +1 @@
module github.com/gogf/gf module github.com/gogf/gf