-
Notifications
You must be signed in to change notification settings - Fork 178
/
programs.go
156 lines (124 loc) · 3.97 KB
/
programs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package programs
import (
"sync"
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/interpreter"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/fvm/state"
)
type ContractUpdateKey struct {
Address flow.Address
Name string
}
type ContractUpdate struct {
ContractUpdateKey
Code []byte
}
type ProgramEntry struct {
Location common.Location
Program *interpreter.Program
State *state.State
}
type ProgramGetFunc func(location common.Location) (*ProgramEntry, bool)
func emptyProgramGetFunc(_ common.Location) (*ProgramEntry, bool) {
return nil, false
}
// Programs is a cumulative cache-like storage for Programs helping speed up execution of Cadence
// Programs don't evict elements at will, like a typical cache would, but it does it only
// during a cleanup method, which must be called only when the Cadence execution has finished.
// It it also fork-aware, support cheap creation of children capturing local changes.
type Programs struct {
lock sync.RWMutex
programs map[common.LocationID]ProgramEntry
parentFunc ProgramGetFunc
cleaned bool
}
func NewEmptyPrograms() *Programs {
return &Programs{
programs: map[common.LocationID]ProgramEntry{},
parentFunc: emptyProgramGetFunc,
}
}
func (p *Programs) ChildPrograms() *Programs {
return &Programs{
programs: map[common.LocationID]ProgramEntry{},
parentFunc: func(location common.Location) (*ProgramEntry, bool) {
return p.get(location)
},
}
}
// Get returns stored program, state which contains changes which correspond to loading this program,
// and boolean indicating if the value was found
func (p *Programs) Get(location common.Location) (*interpreter.Program, *state.State, bool) {
p.lock.RLock()
defer p.lock.RUnlock()
programEntry, has := p.get(location)
if has {
return programEntry.Program, programEntry.State, true
}
return nil, nil, false
}
func (p *Programs) get(location common.Location) (*ProgramEntry, bool) {
programEntry, ok := p.programs[location.ID()]
if !ok {
parentEntry, has := p.parentFunc(location)
if has {
return parentEntry, true
}
return nil, false
}
return &programEntry, true
}
func (p *Programs) Set(location common.Location, program *interpreter.Program, state *state.State) {
p.lock.Lock()
defer p.lock.Unlock()
p.programs[location.ID()] = ProgramEntry{
Location: location,
Program: program,
State: state,
}
}
// HasChanges indicates if any changes has been introduced
// essentially telling if this object is identical to its parent
func (p *Programs) HasChanges() bool {
return len(p.programs) > 0 || p.cleaned
}
// ForceCleanup is used to force a complete cleanup
// It exists temporarily to facilitate a temporary measure which can retry
// a transaction in case checking fails
// It should be gone when the extra retry is gone
func (p *Programs) ForceCleanup() {
p.lock.Lock()
defer p.lock.Unlock()
p.cleaned = true
// Stop using parent's data to prevent
// infinite chaining of objects
p.parentFunc = emptyProgramGetFunc
// start with empty storage
p.programs = make(map[common.LocationID]ProgramEntry)
}
func (p *Programs) Cleanup(changedContracts []ContractUpdateKey) {
p.lock.Lock()
defer p.lock.Unlock()
// In mature system, we would track dependencies between contracts
// and invalidate only affected ones, possibly setting them to
// nil so they will override parent's data, but for now
// just throw everything away and use a special flag for this
if len(changedContracts) > 0 {
p.cleaned = true
// Sop using parent's data to prevent
// infinite chaining of objects
p.parentFunc = emptyProgramGetFunc
// start with empty storage
p.programs = make(map[common.LocationID]ProgramEntry)
return
}
// However, if none of the programs were changed
// we remove all the non AddressLocation data
// (those are temporary tx related entries)
for id, entry := range p.programs {
if _, is := entry.Location.(common.AddressLocation); !is {
delete(p.programs, id)
}
}
}