blob: e3bef1583f5304da8334c10b43804981d5c96478 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"fmt"
"reflect"
"sync"
"android.googlesource.com/platform/tools/gpu/framework/id"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/gapid/config"
)
// NewInMemory builds a new in memory database.
func NewInMemory(buildProxy interface{}) Database {
return &memory{
records: map[id.ID]*record{},
buildProxy: buildProxy,
}
}
type record struct {
value interface{}
err error
wait chan struct{}
}
type memory struct {
mutex sync.Mutex
records map[id.ID]*record
buildProxy interface{} // The build context, user-defined.
}
// Implements Database
func (d *memory) Store(ctx log.Context, id id.ID, v interface{}) error {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.store(ctx, id, v)
}
// store function must be called with a locked mutex
func (d *memory) store(ctx log.Context, id id.ID, v interface{}) error {
if v == nil {
panic(fmt.Errorf("Store nil in database (that is bad), id '%v'", id))
}
r, got := d.records[id]
if !got {
d.records[id] = &record{value: v}
} else if config.DebugDatabaseVerify {
if !reflect.DeepEqual(v, r.value) {
return fmt.Errorf("Duplicate object id %v", id)
}
}
return nil
}
// Implements Database
func (d *memory) Resolve(ctx log.Context, id id.ID) (interface{}, error) {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.resolve(ctx, id)
}
// resolve function must be called with a locked mutex
func (d *memory) resolve(ctx log.Context, id id.ID) (interface{}, error) {
r, got := d.records[id]
if !got {
return nil, fmt.Errorf("Resource '%v' not found", id)
}
if r.err != nil {
return r.value, r.err
}
lazy, islazy := r.value.(Lazy)
if !islazy {
return r.value, r.err
}
// transform the id to get the lazy result id
lazyID := LazyOutputID(id)
if r.wait != nil {
// an in progress request, wait for it
d.mutex.Unlock() // unlock before waiting
<-r.wait
d.mutex.Lock() // relock after waiting
// It is possible r.err has changed, as we released the lock.
if r.err != nil {
return r.value, r.err
}
return d.resolve(ctx, lazyID)
}
// must be a first time access to request
r.wait = make(chan struct{})
value, err := func() (interface{}, error) { // func for defer scope
d.mutex.Unlock() // don't build under the lock
defer d.mutex.Lock() // relock after build
return lazy.BuildLazy(ctx, d.buildProxy, d)
}()
if err == nil {
if value == nil {
err = fmt.Errorf("When resolving a lazy object '%v' a nil value "+
"was returned without an associated error. Lazy: %T:%v Builder %T:%v",
id, lazy, lazy, d.buildProxy, d.buildProxy)
} else {
err = d.store(ctx, lazyID, value)
}
}
r.err = err
close(r.wait)
return value, r.err
}
// Implements Database
func (d *memory) Contains(ctx log.Context, id id.ID) (res bool) {
d.mutex.Lock()
defer d.mutex.Unlock()
_, got := d.records[id]
return got
}