Fix error log when loading issues caused by a xorm bug (#7271)
* fix error log when loading issues caused by a xorm bug * upgrade packages * fix fmt * fix Consistency * fix testsrelease/v1.9
parent
baefea311f
commit
aa7c34cf86
@ -0,0 +1,207 @@
|
|||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ping implements driver.Pinger interface
|
||||||
|
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mc.watchCancel(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
if err = mc.writeCommandPacket(comPing); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc.readResultOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginTx implements driver.ConnBeginTx interface
|
||||||
|
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
|
||||||
|
level, err := mapIsolationLevel(opts.Isolation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc.begin(opts.ReadOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := mc.query(query, dargs)
|
||||||
|
if err != nil {
|
||||||
|
mc.finish()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rows.finish = mc.finish
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
return mc.Exec(query, dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := mc.Prepare(query)
|
||||||
|
mc.finish()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
case <-ctx.Done():
|
||||||
|
stmt.Close()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
return stmt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := stmt.query(dargs)
|
||||||
|
if err != nil {
|
||||||
|
stmt.mc.finish()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rows.finish = stmt.mc.finish
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer stmt.mc.finish()
|
||||||
|
|
||||||
|
return stmt.Exec(dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
|
||||||
|
if mc.watching {
|
||||||
|
// Reach here if canceled,
|
||||||
|
// so the connection is already invalid
|
||||||
|
mc.cleanup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// When ctx is already cancelled, don't watch it.
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// When ctx is not cancellable, don't watch it.
|
||||||
|
if ctx.Done() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// When watcher is not alive, can't watch it.
|
||||||
|
if mc.watcher == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.watching = true
|
||||||
|
mc.watcher <- ctx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) startWatcher() {
|
||||||
|
watcher := make(chan mysqlContext, 1)
|
||||||
|
mc.watcher = watcher
|
||||||
|
finished := make(chan struct{})
|
||||||
|
mc.finished = finished
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var ctx mysqlContext
|
||||||
|
select {
|
||||||
|
case ctx = <-watcher:
|
||||||
|
case <-mc.closech:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
mc.cancel(ctx.Err())
|
||||||
|
case <-finished:
|
||||||
|
case <-mc.closech:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
|
||||||
|
nv.Value, err = converter{}.ConvertValue(nv.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetSession implements driver.SessionResetter.
|
||||||
|
// (From Go 1.10)
|
||||||
|
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
Rand: c.Rand,
|
||||||
|
Time: c.Time,
|
||||||
|
Certificates: c.Certificates,
|
||||||
|
NameToCertificate: c.NameToCertificate,
|
||||||
|
GetCertificate: c.GetCertificate,
|
||||||
|
RootCAs: c.RootCAs,
|
||||||
|
NextProtos: c.NextProtos,
|
||||||
|
ServerName: c.ServerName,
|
||||||
|
ClientAuth: c.ClientAuth,
|
||||||
|
ClientCAs: c.ClientCAs,
|
||||||
|
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||||
|
CipherSuites: c.CipherSuites,
|
||||||
|
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||||
|
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||||
|
SessionTicketKey: c.SessionTicketKey,
|
||||||
|
ClientSessionCache: c.ClientSessionCache,
|
||||||
|
MinVersion: c.MinVersion,
|
||||||
|
MaxVersion: c.MaxVersion,
|
||||||
|
CurvePreferences: c.CurvePreferences,
|
||||||
|
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
|
||||||
|
Renegotiation: c.Renegotiation,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||||
|
return c.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
|
||||||
|
dargs := make([]driver.Value, len(named))
|
||||||
|
for n, param := range named {
|
||||||
|
if len(param.Name) > 0 {
|
||||||
|
// TODO: support the use of Named Parameters #561
|
||||||
|
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
|
||||||
|
}
|
||||||
|
dargs[n] = param.Value
|
||||||
|
}
|
||||||
|
return dargs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
|
||||||
|
switch sql.IsolationLevel(level) {
|
||||||
|
case sql.LevelRepeatableRead:
|
||||||
|
return "REPEATABLE READ", nil
|
||||||
|
case sql.LevelReadCommitted:
|
||||||
|
return "READ COMMITTED", nil
|
||||||
|
case sql.LevelReadUncommitted:
|
||||||
|
return "READ UNCOMMITTED", nil
|
||||||
|
case sql.LevelSerializable:
|
||||||
|
return "SERIALIZABLE", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
|
||||||
|
}
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
module "github.com/go-xorm/builder"
|
|
@ -1,15 +0,0 @@
|
|||||||
dependencies:
|
|
||||||
override:
|
|
||||||
# './...' is a relative pattern which means all subdirectories
|
|
||||||
- go get -t -d -v ./...
|
|
||||||
- go build -v
|
|
||||||
|
|
||||||
database:
|
|
||||||
override:
|
|
||||||
- mysql -u root -e "CREATE DATABASE core_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci"
|
|
||||||
|
|
||||||
test:
|
|
||||||
override:
|
|
||||||
# './...' is a relative pattern which means all subdirectories
|
|
||||||
- go test -v -race
|
|
||||||
- go test -v -race --dbtype=sqlite3
|
|
@ -1,401 +0,0 @@
|
|||||||
package core
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultCacheSize = 200
|
|
||||||
)
|
|
||||||
|
|
||||||
func MapToSlice(query string, mp interface{}) (string, []interface{}, error) {
|
|
||||||
vv := reflect.ValueOf(mp)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
|
|
||||||
return "", []interface{}{}, ErrNoMapPointer
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, 0, len(vv.Elem().MapKeys()))
|
|
||||||
var err error
|
|
||||||
query = re.ReplaceAllStringFunc(query, func(src string) string {
|
|
||||||
v := vv.Elem().MapIndex(reflect.ValueOf(src[1:]))
|
|
||||||
if !v.IsValid() {
|
|
||||||
err = fmt.Errorf("map key %s is missing", src[1:])
|
|
||||||
} else {
|
|
||||||
args = append(args, v.Interface())
|
|
||||||
}
|
|
||||||
return "?"
|
|
||||||
})
|
|
||||||
|
|
||||||
return query, args, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func StructToSlice(query string, st interface{}) (string, []interface{}, error) {
|
|
||||||
vv := reflect.ValueOf(st)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
|
|
||||||
return "", []interface{}{}, ErrNoStructPointer
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, 0)
|
|
||||||
var err error
|
|
||||||
query = re.ReplaceAllStringFunc(query, func(src string) string {
|
|
||||||
fv := vv.Elem().FieldByName(src[1:]).Interface()
|
|
||||||
if v, ok := fv.(driver.Valuer); ok {
|
|
||||||
var value driver.Value
|
|
||||||
value, err = v.Value()
|
|
||||||
if err != nil {
|
|
||||||
return "?"
|
|
||||||
}
|
|
||||||
args = append(args, value)
|
|
||||||
} else {
|
|
||||||
args = append(args, fv)
|
|
||||||
}
|
|
||||||
return "?"
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", []interface{}{}, err
|
|
||||||
}
|
|
||||||
return query, args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type cacheStruct struct {
|
|
||||||
value reflect.Value
|
|
||||||
idx int
|
|
||||||
}
|
|
||||||
|
|
||||||
type DB struct {
|
|
||||||
*sql.DB
|
|
||||||
Mapper IMapper
|
|
||||||
reflectCache map[reflect.Type]*cacheStruct
|
|
||||||
reflectCacheMutex sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func Open(driverName, dataSourceName string) (*DB, error) {
|
|
||||||
db, err := sql.Open(driverName, dataSourceName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &DB{
|
|
||||||
DB: db,
|
|
||||||
Mapper: NewCacheMapper(&SnakeMapper{}),
|
|
||||||
reflectCache: make(map[reflect.Type]*cacheStruct),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromDB(db *sql.DB) *DB {
|
|
||||||
return &DB{
|
|
||||||
DB: db,
|
|
||||||
Mapper: NewCacheMapper(&SnakeMapper{}),
|
|
||||||
reflectCache: make(map[reflect.Type]*cacheStruct),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) reflectNew(typ reflect.Type) reflect.Value {
|
|
||||||
db.reflectCacheMutex.Lock()
|
|
||||||
defer db.reflectCacheMutex.Unlock()
|
|
||||||
cs, ok := db.reflectCache[typ]
|
|
||||||
if !ok || cs.idx+1 > DefaultCacheSize-1 {
|
|
||||||
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0}
|
|
||||||
db.reflectCache[typ] = cs
|
|
||||||
} else {
|
|
||||||
cs.idx = cs.idx + 1
|
|
||||||
}
|
|
||||||
return cs.value.Index(cs.idx).Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
|
|
||||||
rows, err := db.DB.Query(query, args...)
|
|
||||||
if err != nil {
|
|
||||||
if rows != nil {
|
|
||||||
rows.Close()
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Rows{rows, db}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) {
|
|
||||||
query, args, err := MapToSlice(query, mp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return db.Query(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) {
|
|
||||||
query, args, err := StructToSlice(query, st)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return db.Query(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) QueryRow(query string, args ...interface{}) *Row {
|
|
||||||
rows, err := db.Query(query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return &Row{nil, err}
|
|
||||||
}
|
|
||||||
return &Row{rows, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) QueryRowMap(query string, mp interface{}) *Row {
|
|
||||||
query, args, err := MapToSlice(query, mp)
|
|
||||||
if err != nil {
|
|
||||||
return &Row{nil, err}
|
|
||||||
}
|
|
||||||
return db.QueryRow(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) QueryRowStruct(query string, st interface{}) *Row {
|
|
||||||
query, args, err := StructToSlice(query, st)
|
|
||||||
if err != nil {
|
|
||||||
return &Row{nil, err}
|
|
||||||
}
|
|
||||||
return db.QueryRow(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stmt struct {
|
|
||||||
*sql.Stmt
|
|
||||||
db *DB
|
|
||||||
names map[string]int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) Prepare(query string) (*Stmt, error) {
|
|
||||||
names := make(map[string]int)
|
|
||||||
var i int
|
|
||||||
query = re.ReplaceAllStringFunc(query, func(src string) string {
|
|
||||||
names[src[1:]] = i
|
|
||||||
i += 1
|
|
||||||
return "?"
|
|
||||||
})
|
|
||||||
|
|
||||||
stmt, err := db.DB.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Stmt{stmt, db, names}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) {
|
|
||||||
vv := reflect.ValueOf(mp)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
|
|
||||||
return nil, errors.New("mp should be a map's pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, len(s.names))
|
|
||||||
for k, i := range s.names {
|
|
||||||
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface()
|
|
||||||
}
|
|
||||||
return s.Stmt.Exec(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) {
|
|
||||||
vv := reflect.ValueOf(st)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
|
|
||||||
return nil, errors.New("mp should be a map's pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, len(s.names))
|
|
||||||
for k, i := range s.names {
|
|
||||||
args[i] = vv.Elem().FieldByName(k).Interface()
|
|
||||||
}
|
|
||||||
return s.Stmt.Exec(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
|
|
||||||
rows, err := s.Stmt.Query(args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Rows{rows, s.db}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) {
|
|
||||||
vv := reflect.ValueOf(mp)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
|
|
||||||
return nil, errors.New("mp should be a map's pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, len(s.names))
|
|
||||||
for k, i := range s.names {
|
|
||||||
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Query(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) {
|
|
||||||
vv := reflect.ValueOf(st)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
|
|
||||||
return nil, errors.New("mp should be a map's pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, len(s.names))
|
|
||||||
for k, i := range s.names {
|
|
||||||
args[i] = vv.Elem().FieldByName(k).Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Query(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stmt) QueryRow(args ...interface{}) *Row {
|
|
||||||
rows, err := s.Query(args...)
|
|
||||||
return &Row{rows, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stmt) QueryRowMap(mp interface{}) *Row {
|
|
||||||
vv := reflect.ValueOf(mp)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
|
|
||||||
return &Row{nil, errors.New("mp should be a map's pointer")}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, len(s.names))
|
|
||||||
for k, i := range s.names {
|
|
||||||
args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.QueryRow(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stmt) QueryRowStruct(st interface{}) *Row {
|
|
||||||
vv := reflect.ValueOf(st)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct {
|
|
||||||
return &Row{nil, errors.New("st should be a struct's pointer")}
|
|
||||||
}
|
|
||||||
|
|
||||||
args := make([]interface{}, len(s.names))
|
|
||||||
for k, i := range s.names {
|
|
||||||
args[i] = vv.Elem().FieldByName(k).Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.QueryRow(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
re = regexp.MustCompile(`[?](\w+)`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// insert into (name) values (?)
|
|
||||||
// insert into (name) values (?name)
|
|
||||||
func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) {
|
|
||||||
query, args, err := MapToSlice(query, mp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return db.DB.Exec(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) {
|
|
||||||
query, args, err := StructToSlice(query, st)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return db.DB.Exec(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type EmptyScanner struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (EmptyScanner) Scan(src interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tx struct {
|
|
||||||
*sql.Tx
|
|
||||||
db *DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) Begin() (*Tx, error) {
|
|
||||||
tx, err := db.DB.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Tx{tx, db}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) Prepare(query string) (*Stmt, error) {
|
|
||||||
names := make(map[string]int)
|
|
||||||
var i int
|
|
||||||
query = re.ReplaceAllStringFunc(query, func(src string) string {
|
|
||||||
names[src[1:]] = i
|
|
||||||
i += 1
|
|
||||||
return "?"
|
|
||||||
})
|
|
||||||
|
|
||||||
stmt, err := tx.Tx.Prepare(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Stmt{stmt, tx.db, names}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) Stmt(stmt *Stmt) *Stmt {
|
|
||||||
// TODO:
|
|
||||||
return stmt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) {
|
|
||||||
query, args, err := MapToSlice(query, mp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tx.Tx.Exec(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) {
|
|
||||||
query, args, err := StructToSlice(query, st)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tx.Tx.Exec(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {
|
|
||||||
rows, err := tx.Tx.Query(query, args...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Rows{rows, tx.db}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) {
|
|
||||||
query, args, err := MapToSlice(query, mp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tx.Query(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) {
|
|
||||||
query, args, err := StructToSlice(query, st)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tx.Query(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) QueryRow(query string, args ...interface{}) *Row {
|
|
||||||
rows, err := tx.Query(query, args...)
|
|
||||||
return &Row{rows, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row {
|
|
||||||
query, args, err := MapToSlice(query, mp)
|
|
||||||
if err != nil {
|
|
||||||
return &Row{nil, err}
|
|
||||||
}
|
|
||||||
return tx.QueryRow(query, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row {
|
|
||||||
query, args, err := StructToSlice(query, st)
|
|
||||||
if err != nil {
|
|
||||||
return &Row{nil, err}
|
|
||||||
}
|
|
||||||
return tx.QueryRow(query, args...)
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
module "github.com/go-xorm/core"
|
|
@ -1,41 +0,0 @@
|
|||||||
dependencies:
|
|
||||||
override:
|
|
||||||
# './...' is a relative pattern which means all subdirectories
|
|
||||||
- go get -t -d -v ./...
|
|
||||||
- go get -t -d -v github.com/go-xorm/tests
|
|
||||||
- go get -u github.com/go-xorm/core
|
|
||||||
- go get -u github.com/go-xorm/builder
|
|
||||||
- go build -v
|
|
||||||
|
|
||||||
database:
|
|
||||||
override:
|
|
||||||
- mysql -u root -e "CREATE DATABASE xorm_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci"
|
|
||||||
- mysql -u root -e "CREATE DATABASE xorm_test1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci"
|
|
||||||
- mysql -u root -e "CREATE DATABASE xorm_test2 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci"
|
|
||||||
- mysql -u root -e "CREATE DATABASE xorm_test3 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci"
|
|
||||||
- createdb -p 5432 -e -U postgres xorm_test
|
|
||||||
- createdb -p 5432 -e -U postgres xorm_test1
|
|
||||||
- createdb -p 5432 -e -U postgres xorm_test2
|
|
||||||
- createdb -p 5432 -e -U postgres xorm_test3
|
|
||||||
- psql xorm_test postgres -c "create schema xorm"
|
|
||||||
|
|
||||||
test:
|
|
||||||
override:
|
|
||||||
# './...' is a relative pattern which means all subdirectories
|
|
||||||
- go get -u github.com/wadey/gocovmerge
|
|
||||||
- go test -v -race -db="sqlite3" -conn_str="./test.db" -coverprofile=coverage1-1.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="sqlite3" -conn_str="./test.db" -cache=true -coverprofile=coverage1-2.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -coverprofile=coverage2-1.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="mysql" -conn_str="root:@/xorm_test" -cache=true -coverprofile=coverage2-2.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -coverprofile=coverage3-1.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="mymysql" -conn_str="xorm_test/root/" -cache=true -coverprofile=coverage3-2.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -coverprofile=coverage4-1.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -cache=true -coverprofile=coverage4-2.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -coverprofile=coverage5-1.txt -covermode=atomic
|
|
||||||
- go test -v -race -db="postgres" -conn_str="dbname=xorm_test sslmode=disable" -schema=xorm -cache=true -coverprofile=coverage5-2.txt -covermode=atomic
|
|
||||||
- gocovmerge coverage1-1.txt coverage1-2.txt coverage2-1.txt coverage2-2.txt coverage3-1.txt coverage3-2.txt coverage4-1.txt coverage4-2.txt coverage5-1.txt coverage5-2.txt > coverage.txt
|
|
||||||
- cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./sqlite3.sh
|
|
||||||
- cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./mysql.sh
|
|
||||||
- cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./postgres.sh
|
|
||||||
post:
|
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
|
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2019 The Xorm Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package xorm
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// Context creates a session with the context
|
||||||
|
func (engine *Engine) Context(ctx context.Context) *Session {
|
||||||
|
session := engine.NewSession()
|
||||||
|
session.isAutoClose = true
|
||||||
|
return session.Context(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultContext set the default context
|
||||||
|
func (engine *Engine) SetDefaultContext(ctx context.Context) {
|
||||||
|
engine.defaultContext = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// PingContext tests if database is alive
|
||||||
|
func (engine *Engine) PingContext(ctx context.Context) error {
|
||||||
|
session := engine.NewSession()
|
||||||
|
defer session.Close()
|
||||||
|
return session.PingContext(ctx)
|
||||||
|
}
|
@ -1,24 +1,24 @@
|
|||||||
module github.com/go-xorm/xorm
|
module github.com/go-xorm/xorm
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cloud.google.com/go v0.34.0 // indirect
|
||||||
github.com/cockroachdb/apd v1.1.0 // indirect
|
github.com/cockroachdb/apd v1.1.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f
|
github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952
|
||||||
github.com/go-sql-driver/mysql v1.4.0
|
github.com/go-sql-driver/mysql v1.4.1
|
||||||
github.com/go-xorm/builder v0.3.2
|
github.com/google/go-cmp v0.2.0 // indirect
|
||||||
github.com/go-xorm/core v0.6.0
|
|
||||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a // indirect
|
|
||||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
|
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
|
||||||
github.com/jackc/pgx v3.2.0+incompatible
|
github.com/jackc/pgx v3.3.0+incompatible
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/lib/pq v1.0.0
|
github.com/lib/pq v1.0.0
|
||||||
github.com/mattn/go-sqlite3 v1.9.0
|
github.com/mattn/go-sqlite3 v1.10.0
|
||||||
github.com/pkg/errors v0.8.0 // indirect
|
github.com/pkg/errors v0.8.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/satori/go.uuid v1.2.0 // indirect
|
github.com/satori/go.uuid v1.2.0 // indirect
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/ziutek/mymysql v1.5.4
|
github.com/ziutek/mymysql v1.5.4
|
||||||
|
golang.org/x/crypto v0.0.0-20190122013713-64072686203f // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
gopkg.in/stretchr/testify.v1 v1.2.2
|
xorm.io/builder v0.3.5
|
||||||
|
xorm.io/core v0.6.3
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2019 The Xorm Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package xorm
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// JSONInterface represents an interface to handle json data
|
||||||
|
type JSONInterface interface {
|
||||||
|
Marshal(v interface{}) ([]byte, error)
|
||||||
|
Unmarshal(data []byte, v interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultJSONHandler default json handler
|
||||||
|
DefaultJSONHandler JSONInterface = StdJSON{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// StdJSON implements JSONInterface via encoding/json
|
||||||
|
type StdJSON struct{}
|
||||||
|
|
||||||
|
// Marshal implements JSONInterface
|
||||||
|
func (StdJSON) Marshal(v interface{}) ([]byte, error) {
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal implements JSONInterface
|
||||||
|
func (StdJSON) Unmarshal(data []byte, v interface{}) error {
|
||||||
|
return json.Unmarshal(data, v)
|
||||||
|
}
|
@ -1,18 +1,15 @@
|
|||||||
// Copyright 2017 The Xorm Authors. All rights reserved.
|
// Copyright 2019 The Xorm Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package xorm
|
package xorm
|
||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
// PingContext tests if database is alive
|
// Context sets the context on this session
|
||||||
func (engine *Engine) PingContext(ctx context.Context) error {
|
func (session *Session) Context(ctx context.Context) *Session {
|
||||||
session := engine.NewSession()
|
session.ctx = ctx
|
||||||
defer session.Close()
|
return session
|
||||||
return session.PingContext(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PingContext test if database is ok
|
// PingContext test if database is ok
|
@ -1 +1 @@
|
|||||||
go test -db=mssql -conn_str="server=192.168.1.58;user id=sa;password=123456;database=xorm_test"
|
go test -db=mssql -conn_str="server=localhost;user id=sa;password=yourStrong(!)Password;database=xorm_test"
|
@ -0,0 +1 @@
|
|||||||
|
go test -db=mysql -conn_str="root:@tcp(localhost:4000)/xorm_test" -ignore_select_update=true
|
@ -1,682 +0,0 @@
|
|||||||
// Copyright 2011 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !appengine
|
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
netcontext "golang.org/x/net/context"
|
|
||||||
|
|
||||||
basepb "google.golang.org/appengine/internal/base"
|
|
||||||
logpb "google.golang.org/appengine/internal/log"
|
|
||||||
remotepb "google.golang.org/appengine/internal/remote_api"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
apiPath = "/rpc_http"
|
|
||||||
defaultTicketSuffix = "/default.20150612t184001.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Incoming headers.
|
|
||||||
ticketHeader = http.CanonicalHeaderKey("X-AppEngine-API-Ticket")
|
|
||||||
dapperHeader = http.CanonicalHeaderKey("X-Google-DapperTraceInfo")
|
|
||||||
traceHeader = http.CanonicalHeaderKey("X-Cloud-Trace-Context")
|
|
||||||
curNamespaceHeader = http.CanonicalHeaderKey("X-AppEngine-Current-Namespace")
|
|
||||||
userIPHeader = http.CanonicalHeaderKey("X-AppEngine-User-IP")
|
|
||||||
remoteAddrHeader = http.CanonicalHeaderKey("X-AppEngine-Remote-Addr")
|
|
||||||
|
|
||||||
// Outgoing headers.
|
|
||||||
apiEndpointHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Endpoint")
|
|
||||||
apiEndpointHeaderValue = []string{"app-engine-apis"}
|
|
||||||
apiMethodHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Method")
|
|
||||||
apiMethodHeaderValue = []string{"/VMRemoteAPI.CallRemoteAPI"}
|
|
||||||
apiDeadlineHeader = http.CanonicalHeaderKey("X-Google-RPC-Service-Deadline")
|
|
||||||
apiContentType = http.CanonicalHeaderKey("Content-Type")
|
|
||||||
apiContentTypeValue = []string{"application/octet-stream"}
|
|
||||||
logFlushHeader = http.CanonicalHeaderKey("X-AppEngine-Log-Flush-Count")
|
|
||||||
|
|
||||||
apiHTTPClient = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
Dial: limitDial,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultTicketOnce sync.Once
|
|
||||||
defaultTicket string
|
|
||||||
)
|
|
||||||
|
|
||||||
func apiURL() *url.URL {
|
|
||||||
host, port := "appengine.googleapis.internal", "10001"
|
|
||||||
if h := os.Getenv("API_HOST"); h != "" {
|
|
||||||
host = h
|
|
||||||
}
|
|
||||||
if p := os.Getenv("API_PORT"); p != "" {
|
|
||||||
port = p
|
|
||||||
}
|
|
||||||
return &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: host + ":" + port,
|
|
||||||
Path: apiPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
c := &context{
|
|
||||||
req: r,
|
|
||||||
outHeader: w.Header(),
|
|
||||||
apiURL: apiURL(),
|
|
||||||
}
|
|
||||||
stopFlushing := make(chan int)
|
|
||||||
|
|
||||||
ctxs.Lock()
|
|
||||||
ctxs.m[r] = c
|
|
||||||
ctxs.Unlock()
|
|
||||||
defer func() {
|
|
||||||
ctxs.Lock()
|
|
||||||
delete(ctxs.m, r)
|
|
||||||
ctxs.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Patch up RemoteAddr so it looks reasonable.
|
|
||||||
if addr := r.Header.Get(userIPHeader); addr != "" {
|
|
||||||
r.RemoteAddr = addr
|
|
||||||
} else if addr = r.Header.Get(remoteAddrHeader); addr != "" {
|
|
||||||
r.RemoteAddr = addr
|
|
||||||
} else {
|
|
||||||
// Should not normally reach here, but pick a sensible default anyway.
|
|
||||||
r.RemoteAddr = "127.0.0.1"
|
|
||||||
}
|
|
||||||
// The address in the headers will most likely be of these forms:
|
|
||||||
// 123.123.123.123
|
|
||||||
// 2001:db8::1
|
|
||||||
// net/http.Request.RemoteAddr is specified to be in "IP:port" form.
|
|
||||||
if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
|
|
||||||
// Assume the remote address is only a host; add a default port.
|
|
||||||
r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start goroutine responsible for flushing app logs.
|
|
||||||
// This is done after adding c to ctx.m (and stopped before removing it)
|
|
||||||
// because flushing logs requires making an API call.
|
|
||||||
go c.logFlusher(stopFlushing)
|
|
||||||
|
|
||||||
executeRequestSafely(c, r)
|
|
||||||
c.outHeader = nil // make sure header changes aren't respected any more
|
|
||||||
|
|
||||||
stopFlushing <- 1 // any logging beyond this point will be dropped
|
|
||||||
|
|
||||||
// Flush any pending logs asynchronously.
|
|
||||||
c.pendingLogs.Lock()
|
|
||||||
flushes := c.pendingLogs.flushes
|
|
||||||
if len(c.pendingLogs.lines) > 0 {
|
|
||||||
flushes++
|
|
||||||
}
|
|
||||||
c.pendingLogs.Unlock()
|
|
||||||
go c.flushLog(false)
|
|
||||||
w.Header().Set(logFlushHeader, strconv.Itoa(flushes))
|
|
||||||
|
|
||||||
// Avoid nil Write call if c.Write is never called.
|
|
||||||
if c.outCode != 0 {
|
|
||||||
w.WriteHeader(c.outCode)
|
|
||||||
}
|
|
||||||
if c.outBody != nil {
|
|
||||||
w.Write(c.outBody)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func executeRequestSafely(c *context, r *http.Request) {
|
|
||||||
defer func() {
|
|
||||||
if x := recover(); x != nil {
|
|
||||||
logf(c, 4, "%s", renderPanic(x)) // 4 == critical
|
|
||||||
c.outCode = 500
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
http.DefaultServeMux.ServeHTTP(c, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderPanic(x interface{}) string {
|
|
||||||
buf := make([]byte, 16<<10) // 16 KB should be plenty
|
|
||||||
buf = buf[:runtime.Stack(buf, false)]
|
|
||||||
|
|
||||||
// Remove the first few stack frames:
|
|
||||||
// this func
|
|
||||||
// the recover closure in the caller
|
|
||||||
// That will root the stack trace at the site of the panic.
|
|
||||||
const (
|
|
||||||
skipStart = "internal.renderPanic"
|
|
||||||
skipFrames = 2
|
|
||||||
)
|
|
||||||
start := bytes.Index(buf, []byte(skipStart))
|
|
||||||
p := start
|
|
||||||
for i := 0; i < skipFrames*2 && p+1 < len(buf); i++ {
|
|
||||||
p = bytes.IndexByte(buf[p+1:], '\n') + p + 1
|
|
||||||
if p < 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p >= 0 {
|
|
||||||
// buf[start:p+1] is the block to remove.
|
|
||||||
// Copy buf[p+1:] over buf[start:] and shrink buf.
|
|
||||||
copy(buf[start:], buf[p+1:])
|
|
||||||
buf = buf[:len(buf)-(p+1-start)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add panic heading.
|
|
||||||
head := fmt.Sprintf("panic: %v\n\n", x)
|
|
||||||
if len(head) > len(buf) {
|
|
||||||
// Extremely unlikely to happen.
|
|
||||||
return head
|
|
||||||
}
|
|
||||||
copy(buf[len(head):], buf)
|
|
||||||
copy(buf, head)
|
|
||||||
|
|
||||||
return string(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ctxs = struct {
|
|
||||||
sync.Mutex
|
|
||||||
m map[*http.Request]*context
|
|
||||||
bg *context // background context, lazily initialized
|
|
||||||
// dec is used by tests to decorate the netcontext.Context returned
|
|
||||||
// for a given request. This allows tests to add overrides (such as
|
|
||||||
// WithAppIDOverride) to the context. The map is nil outside tests.
|
|
||||||
dec map[*http.Request]func(netcontext.Context) netcontext.Context
|
|
||||||
}{
|
|
||||||
m: make(map[*http.Request]*context),
|
|
||||||
}
|
|
||||||
|
|
||||||
// context represents the context of an in-flight HTTP request.
|
|
||||||
// It implements the appengine.Context and http.ResponseWriter interfaces.
|
|
||||||
type context struct {
|
|
||||||
req *http.Request
|
|
||||||
|
|
||||||
outCode int
|
|
||||||
outHeader http.Header
|
|
||||||
outBody []byte
|
|
||||||
|
|
||||||
pendingLogs struct {
|
|
||||||
sync.Mutex
|
|
||||||
lines []*logpb.UserAppLogLine
|
|
||||||
flushes int
|
|
||||||
}
|
|
||||||
|
|
||||||
apiURL *url.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
var contextKey = "holds a *context"
|
|
||||||
|
|
||||||
// fromContext returns the App Engine context or nil if ctx is not
|
|
||||||
// derived from an App Engine context.
|
|
||||||
func fromContext(ctx netcontext.Context) *context {
|
|
||||||
c, _ := ctx.Value(&contextKey).(*context)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func withContext(parent netcontext.Context, c *context) netcontext.Context {
|
|
||||||
ctx := netcontext.WithValue(parent, &contextKey, c)
|
|
||||||
if ns := c.req.Header.Get(curNamespaceHeader); ns != "" {
|
|
||||||
ctx = withNamespace(ctx, ns)
|
|
||||||
}
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func toContext(c *context) netcontext.Context {
|
|
||||||
return withContext(netcontext.Background(), c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IncomingHeaders(ctx netcontext.Context) http.Header {
|
|
||||||
if c := fromContext(ctx); c != nil {
|
|
||||||
return c.req.Header
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReqContext(req *http.Request) netcontext.Context {
|
|
||||||
return WithContext(netcontext.Background(), req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithContext(parent netcontext.Context, req *http.Request) netcontext.Context {
|
|
||||||
ctxs.Lock()
|
|
||||||
c := ctxs.m[req]
|
|
||||||
d := ctxs.dec[req]
|
|
||||||
ctxs.Unlock()
|
|
||||||
|
|
||||||
if d != nil {
|
|
||||||
parent = d(parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c == nil {
|
|
||||||
// Someone passed in an http.Request that is not in-flight.
|
|
||||||
// We panic here rather than panicking at a later point
|
|
||||||
// so that stack traces will be more sensible.
|
|
||||||
log.Panic("appengine: NewContext passed an unknown http.Request")
|
|
||||||
}
|
|
||||||
return withContext(parent, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultTicket returns a ticket used for background context or dev_appserver.
|
|
||||||
func DefaultTicket() string {
|
|
||||||
defaultTicketOnce.Do(func() {
|
|
||||||
if IsDevAppServer() {
|
|
||||||
defaultTicket = "testapp" + defaultTicketSuffix
|
|
||||||
return
|
|
||||||
}
|
|
||||||
appID := partitionlessAppID()
|
|
||||||
escAppID := strings.Replace(strings.Replace(appID, ":", "_", -1), ".", "_", -1)
|
|
||||||
majVersion := VersionID(nil)
|
|
||||||
if i := strings.Index(majVersion, "."); i > 0 {
|
|
||||||
majVersion = majVersion[:i]
|
|
||||||
}
|
|
||||||
defaultTicket = fmt.Sprintf("%s/%s.%s.%s", escAppID, ModuleName(nil), majVersion, InstanceID())
|
|
||||||
})
|
|
||||||
return defaultTicket
|
|
||||||
}
|
|
||||||
|
|
||||||
func BackgroundContext() netcontext.Context {
|
|
||||||
ctxs.Lock()
|
|
||||||
defer ctxs.Unlock()
|
|
||||||
|
|
||||||
if ctxs.bg != nil {
|
|
||||||
return toContext(ctxs.bg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute background security ticket.
|
|
||||||
ticket := DefaultTicket()
|
|
||||||
|
|
||||||
ctxs.bg = &context{
|
|
||||||
req: &http.Request{
|
|
||||||
Header: http.Header{
|
|
||||||
ticketHeader: []string{ticket},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
apiURL: apiURL(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(dsymonds): Wire up the shutdown handler to do a final flush.
|
|
||||||
go ctxs.bg.logFlusher(make(chan int))
|
|
||||||
|
|
||||||
return toContext(ctxs.bg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterTestRequest registers the HTTP request req for testing, such that
|
|
||||||
// any API calls are sent to the provided URL. It returns a closure to delete
|
|
||||||
// the registration.
|
|
||||||
// It should only be used by aetest package.
|
|
||||||
func RegisterTestRequest(req *http.Request, apiURL *url.URL, decorate func(netcontext.Context) netcontext.Context) (*http.Request, func()) {
|
|
||||||
c := &context{
|
|
||||||
req: req,
|
|
||||||
apiURL: apiURL,
|
|
||||||
}
|
|
||||||
ctxs.Lock()
|
|
||||||
defer ctxs.Unlock()
|
|
||||||
if _, ok := ctxs.m[req]; ok {
|
|
||||||
log.Panic("req already associated with context")
|
|
||||||
}
|
|
||||||
if _, ok := ctxs.dec[req]; ok {
|
|
||||||
log.Panic("req already associated with context")
|
|
||||||
}
|
|
||||||
if ctxs.dec == nil {
|
|
||||||
ctxs.dec = make(map[*http.Request]func(netcontext.Context) netcontext.Context)
|
|
||||||
}
|
|
||||||
ctxs.m[req] = c
|
|
||||||
ctxs.dec[req] = decorate
|
|
||||||
|
|
||||||
return req, func() {
|
|
||||||
ctxs.Lock()
|
|
||||||
delete(ctxs.m, req)
|
|
||||||
delete(ctxs.dec, req)
|
|
||||||
ctxs.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTimeout = &CallError{
|
|
||||||
Detail: "Deadline exceeded",
|
|
||||||
Code: int32(remotepb.RpcError_CANCELLED),
|
|
||||||
Timeout: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *context) Header() http.Header { return c.outHeader }
|
|
||||||
|
|
||||||
// Copied from $GOROOT/src/pkg/net/http/transfer.go. Some response status
|
|
||||||
// codes do not permit a response body (nor response entity headers such as
|
|
||||||
// Content-Length, Content-Type, etc).
|
|
||||||
func bodyAllowedForStatus(status int) bool {
|
|
||||||
switch {
|
|
||||||
case status >= 100 && status <= 199:
|
|
||||||
return false
|
|
||||||
case status == 204:
|
|
||||||
return false
|
|
||||||
case status == 304:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *context) Write(b []byte) (int, error) {
|
|
||||||
if c.outCode == 0 {
|
|
||||||
c.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
if len(b) > 0 && !bodyAllowedForStatus(c.outCode) {
|
|
||||||
return 0, http.ErrBodyNotAllowed
|
|
||||||
}
|
|
||||||
c.outBody = append(c.outBody, b...)
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *context) WriteHeader(code int) {
|
|
||||||
if c.outCode != 0 {
|
|
||||||
logf(c, 3, "WriteHeader called multiple times on request.") // error level
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.outCode = code
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *context) post(body []byte, timeout time.Duration) (b []byte, err error) {
|
|
||||||
hreq := &http.Request{
|
|
||||||
Method: "POST",
|
|
||||||
URL: c.apiURL,
|
|
||||||
Header: http.Header{
|
|
||||||
apiEndpointHeader: apiEndpointHeaderValue,
|
|
||||||
apiMethodHeader: apiMethodHeaderValue,
|
|
||||||
apiContentType: apiContentTypeValue,
|
|
||||||
apiDeadlineHeader: []string{strconv.FormatFloat(timeout.Seconds(), 'f', -1, 64)},
|
|
||||||
},
|
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(body)),
|
|
||||||
ContentLength: int64(len(body)),
|
|
||||||
Host: c.apiURL.Host,
|
|
||||||
}
|
|
||||||
if info := c.req.Header.Get(dapperHeader); info != "" {
|
|
||||||
hreq.Header.Set(dapperHeader, info)
|
|
||||||
}
|
|
||||||
if info := c.req.Header.Get(traceHeader); info != "" {
|
|
||||||
hreq.Header.Set(traceHeader, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := apiHTTPClient.Transport.(*http.Transport)
|
|
||||||
|
|
||||||
var timedOut int32 // atomic; set to 1 if timed out
|
|
||||||
t := time.AfterFunc(timeout, func() {
|
|
||||||
atomic.StoreInt32(&timedOut, 1)
|
|
||||||
tr.CancelRequest(hreq)
|
|
||||||
})
|
|
||||||
defer t.Stop()
|
|
||||||
defer func() {
|
|
||||||
// Check if timeout was exceeded.
|
|
||||||
if atomic.LoadInt32(&timedOut) != 0 {
|
|
||||||
err = errTimeout
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
hresp, err := apiHTTPClient.Do(hreq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &CallError{
|
|
||||||
Detail: fmt.Sprintf("service bridge HTTP failed: %v", err),
|
|
||||||
Code: int32(remotepb.RpcError_UNKNOWN),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer hresp.Body.Close()
|
|
||||||
hrespBody, err := ioutil.ReadAll(hresp.Body)
|
|
||||||
if hresp.StatusCode != 200 {
|
|
||||||
return nil, &CallError{
|
|
||||||
Detail: fmt.Sprintf("service bridge returned HTTP %d (%q)", hresp.StatusCode, hrespBody),
|
|
||||||
Code: int32(remotepb.RpcError_UNKNOWN),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, &CallError{
|
|
||||||
Detail: fmt.Sprintf("service bridge response bad: %v", err),
|
|
||||||
Code: int32(remotepb.RpcError_UNKNOWN),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hrespBody, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Call(ctx netcontext.Context, service, method string, in, out proto.Message) error {
|
|
||||||
if ns := NamespaceFromContext(ctx); ns != "" {
|
|
||||||
if fn, ok := NamespaceMods[service]; ok {
|
|
||||||
fn(in, ns)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f, ctx, ok := callOverrideFromContext(ctx); ok {
|
|
||||||
return f(ctx, service, method, in, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle already-done contexts quickly.
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
c := fromContext(ctx)
|
|
||||||
if c == nil {
|
|
||||||
// Give a good error message rather than a panic lower down.
|
|
||||||
return errNotAppEngineContext
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply transaction modifications if we're in a transaction.
|
|
||||||
if t := transactionFromContext(ctx); t != nil {
|
|
||||||
if t.finished {
|
|
||||||
return errors.New("transaction context has expired")
|
|
||||||
}
|
|
||||||
applyTransaction(in, &t.transaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default RPC timeout is 60s.
|
|
||||||
timeout := 60 * time.Second
|
|
||||||
if deadline, ok := ctx.Deadline(); ok {
|
|
||||||
timeout = deadline.Sub(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := proto.Marshal(in)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ticket := c.req.Header.Get(ticketHeader)
|
|
||||||
// Use a test ticket under test environment.
|
|
||||||
if ticket == "" {
|
|
||||||
if appid := ctx.Value(&appIDOverrideKey); appid != nil {
|
|
||||||
ticket = appid.(string) + defaultTicketSuffix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fall back to use background ticket when the request ticket is not available in Flex or dev_appserver.
|
|
||||||
if ticket == "" {
|
|
||||||
ticket = DefaultTicket()
|
|
||||||
}
|
|
||||||
req := &remotepb.Request{
|
|
||||||
ServiceName: &service,
|
|
||||||
Method: &method,
|
|
||||||
Request: data,
|
|
||||||
RequestId: &ticket,
|
|
||||||
}
|
|
||||||
hreqBody, err := proto.Marshal(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hrespBody, err := c.post(hreqBody, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
res := &remotepb.Response{}
|
|
||||||
if err := proto.Unmarshal(hrespBody, res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if res.RpcError != nil {
|
|
||||||
ce := &CallError{
|
|
||||||
Detail: res.RpcError.GetDetail(),
|
|
||||||
Code: *res.RpcError.Code,
|
|
||||||
}
|
|
||||||
switch remotepb.RpcError_ErrorCode(ce.Code) {
|
|
||||||
case remotepb.RpcError_CANCELLED, remotepb.RpcError_DEADLINE_EXCEEDED:
|
|
||||||
ce.Timeout = true
|
|
||||||
}
|
|
||||||
return ce
|
|
||||||
}
|
|
||||||
if res.ApplicationError != nil {
|
|
||||||
return &APIError{
|
|
||||||
Service: *req.ServiceName,
|
|
||||||
Detail: res.ApplicationError.GetDetail(),
|
|
||||||
Code: *res.ApplicationError.Code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if res.Exception != nil || res.JavaException != nil {
|
|
||||||
// This shouldn't happen, but let's be defensive.
|
|
||||||
return &CallError{
|
|
||||||
Detail: "service bridge returned exception",
|
|
||||||
Code: int32(remotepb.RpcError_UNKNOWN),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return proto.Unmarshal(res.Response, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *context) Request() *http.Request {
|
|
||||||
return c.req
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *context) addLogLine(ll *logpb.UserAppLogLine) {
|
|
||||||
// Truncate long log lines.
|
|
||||||
// TODO(dsymonds): Check if this is still necessary.
|
|
||||||
const lim = 8 << 10
|
|
||||||
if len(*ll.Message) > lim {
|
|
||||||
suffix := fmt.Sprintf("...(length %d)", len(*ll.Message))
|
|
||||||
ll.Message = proto.String((*ll.Message)[:lim-len(suffix)] + suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.pendingLogs.Lock()
|
|
||||||
c.pendingLogs.lines = append(c.pendingLogs.lines, ll)
|
|
||||||
c.pendingLogs.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
var logLevelName = map[int64]string{
|
|
||||||
0: "DEBUG",
|
|
||||||
1: "INFO",
|
|
||||||
2: "WARNING",
|
|
||||||
3: "ERROR",
|
|
||||||
4: "CRITICAL",
|
|
||||||
}
|
|
||||||
|
|
||||||
func logf(c *context, level int64, format string, args ...interface{}) {
|
|
||||||
if c == nil {
|
|
||||||
panic("not an App Engine context")
|
|
||||||
}
|
|
||||||
s := fmt.Sprintf(format, args...)
|
|
||||||
s = strings.TrimRight(s, "\n") // Remove any trailing newline characters.
|
|
||||||
c.addLogLine(&logpb.UserAppLogLine{
|
|
||||||
TimestampUsec: proto.Int64(time.Now().UnixNano() / 1e3),
|
|
||||||
Level: &level,
|
|
||||||
Message: &s,
|
|
||||||
})
|
|
||||||
log.Print(logLevelName[level] + ": " + s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// flushLog attempts to flush any pending logs to the appserver.
|
|
||||||
// It should not be called concurrently.
|
|
||||||
func (c *context) flushLog(force bool) (flushed bool) {
|
|
||||||
c.pendingLogs.Lock()
|
|
||||||
// Grab up to 30 MB. We can get away with up to 32 MB, but let's be cautious.
|
|
||||||
n, rem := 0, 30<<20
|
|
||||||
for ; n < len(c.pendingLogs.lines); n++ {
|
|
||||||
ll := c.pendingLogs.lines[n]
|
|
||||||
// Each log line will require about 3 bytes of overhead.
|
|
||||||
nb := proto.Size(ll) + 3
|
|
||||||
if nb > rem {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
rem -= nb
|
|
||||||
}
|
|
||||||
lines := c.pendingLogs.lines[:n]
|
|
||||||
c.pendingLogs.lines = c.pendingLogs.lines[n:]
|
|
||||||
c.pendingLogs.Unlock()
|
|
||||||
|
|
||||||
if len(lines) == 0 && !force {
|
|
||||||
// Nothing to flush.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
rescueLogs := false
|
|
||||||
defer func() {
|
|
||||||
if rescueLogs {
|
|
||||||
c.pendingLogs.Lock()
|
|
||||||
c.pendingLogs.lines = append(lines, c.pendingLogs.lines...)
|
|
||||||
c.pendingLogs.Unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
buf, err := proto.Marshal(&logpb.UserAppLogGroup{
|
|
||||||
LogLine: lines,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("internal.flushLog: marshaling UserAppLogGroup: %v", err)
|
|
||||||
rescueLogs = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &logpb.FlushRequest{
|
|
||||||
Logs: buf,
|
|
||||||
}
|
|
||||||
res := &basepb.VoidProto{}
|
|
||||||
c.pendingLogs.Lock()
|
|
||||||
c.pendingLogs.flushes++
|
|
||||||
c.pendingLogs.Unlock()
|
|
||||||
if err := Call(toContext(c), "logservice", "Flush", req, res); err != nil {
|
|
||||||
log.Printf("internal.flushLog: Flush RPC: %v", err)
|
|
||||||
rescueLogs = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Log flushing parameters.
|
|
||||||
flushInterval = 1 * time.Second
|
|
||||||
forceFlushInterval = 60 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *context) logFlusher(stop <-chan int) {
|
|
||||||
lastFlush := time.Now()
|
|
||||||
tick := time.NewTicker(flushInterval)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
// Request finished.
|
|
||||||
tick.Stop()
|
|
||||||
return
|
|
||||||
case <-tick.C:
|
|
||||||
force := time.Now().Sub(lastFlush) > forceFlushInterval
|
|
||||||
if c.flushLog(force) {
|
|
||||||
lastFlush = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContextForTesting(req *http.Request) netcontext.Context {
|
|
||||||
return toContext(&context{req: req})
|
|
||||||
}
|
|
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2018 Google LLC. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appenginevm
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
appengineFlex = true
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
// MainPath stores the file path of the main package. On App Engine Standard
|
||||||
|
// using Go version 1.9 and below, this will be unset. On App Engine Flex and
|
||||||
|
// App Engine Standard second-gen (Go 1.11 and above), this will be the
|
||||||
|
// filepath to package main.
|
||||||
|
var MainPath string
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue