Merge lp:~jameinel/juju-core/cache-facade-instances into lp:~go-bot/juju-core/trunk

Proposed by John A Meinel
Status: Work in progress
Proposed branch: lp:~jameinel/juju-core/cache-facade-instances
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~jameinel/juju-core/api-srvRoot-ensures-type
Diff against target: 178 lines (+105/-28)
3 files modified
state/apiserver/export_test.go (+5/-4)
state/apiserver/root.go (+40/-24)
state/apiserver/root_test.go (+60/-0)
To merge this branch: bzr merge lp:~jameinel/juju-core/cache-facade-instances
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+220937@code.launchpad.net

Description of the change

state/apiserver/root.go: cache active facades

Since I made Client no longer special, it was now being created again
for every request, which is a bit inefficient. However, since we now
have a centralized location where requests go through, it is pretty easy
to add caching there. And now all Facades are cached. It even comes with
a test that we don't create new objects for the same (Name, version)
couple, but we do create a different one when version changes.

https://codereview.appspot.com/96600043/

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

Reviewers: mp+220937_code.launchpad.net,

Message:
Please take a look.

Description:
state/apiserver/root.go: cache active facades

Since I made Client no longer special, it was now being created again
for every request, which is a bit inefficient. However, since we now
have a centralized location where requests go through, it is pretty easy
to add caching there. And now all Facades are cached. It even comes with
a test that we don't create new objects for the same (Name, version)
couple, but we do create a different one when version changes.

https://code.launchpad.net/~jameinel/juju-core/cache-facade-instances/+merge/220937

Requires:
https://code.launchpad.net/~jameinel/juju-core/api-srvRoot-ensures-type/+merge/220615

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/96600043/

Affected files (+107, -28 lines):
   A [revision details]
   M state/apiserver/export_test.go
   M state/apiserver/root.go
   M state/apiserver/root_test.go

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
John A Meinel (jameinel) wrote :

moving to WIP since this will all land together.

Unmerged revisions

2760. By John A Meinel

A good thing we added a test, because we weren't actually caching anything. :)

So now we have a proper test case that we can see the objects are
getting cached and reused.

2759. By John A Meinel

Merged api-srvRoot-ensures-type into cache-facade-instances.

2758. By John A Meinel

Merged api-srvRoot-ensures-type into cache-facade-instances.

2757. By John A Meinel

Merged api-srvRoot-ensures-type into cache-facade-instances.

2756. By John A Meinel

Merged api-srvRoot-ensures-type into cache-facade-instances.

2755. By John A Meinel

Merged api-srvRoot-ensures-type into cache-facade-instances.

2754. By John A Meinel

merge up the change to ensure the expectedType of Facade objects.

2753. By John A Meinel

Merge in api-registry-tracks-type, resolve conflict

2752. By John A Meinel

Use a generic object cache keyed on facade name and version.

2751. By John A Meinel

Merge api-use-register-standard-facade.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'state/apiserver/export_test.go'
2--- state/apiserver/export_test.go 2014-05-26 10:15:11 +0000
3+++ state/apiserver/export_test.go 2014-05-26 10:15:12 +0000
4@@ -48,9 +48,10 @@
5 // enough to actually do any RPC calls
6 func TestingSrvRoot(st *state.State) *srvRoot {
7 return &srvRoot{
8- state: st,
9- rpcConn: nil,
10- resources: common.NewResources(),
11- entity: nil,
12+ state: st,
13+ rpcConn: nil,
14+ resources: common.NewResources(),
15+ entity: nil,
16+ objectCache: make(map[facadeKey]interface{}),
17 }
18 }
19
20=== modified file 'state/apiserver/root.go'
21--- state/apiserver/root.go 2014-05-26 10:15:11 +0000
22+++ state/apiserver/root.go 2014-05-26 10:15:12 +0000
23@@ -33,13 +33,19 @@
24 mongoPingInterval = 10 * time.Second
25 )
26
27+type facadeKey struct {
28+ name string
29+ version int
30+}
31+
32 // srvRoot represents a single client's connection to the state
33 // after it has logged in.
34 type srvRoot struct {
35- state *state.State
36- rpcConn *rpc.Conn
37- resources *common.Resources
38- entity taggedAuthenticator
39+ state *state.State
40+ rpcConn *rpc.Conn
41+ resources *common.Resources
42+ entity taggedAuthenticator
43+ objectCache map[facadeKey]interface{}
44 }
45
46 // newSrvRoot creates the client's connection representation
47@@ -47,10 +53,11 @@
48 // connection.
49 func newSrvRoot(root *initialRoot, entity taggedAuthenticator) *srvRoot {
50 r := &srvRoot{
51- state: root.srv.state,
52- rpcConn: root.rpcConn,
53- resources: common.NewResources(),
54- entity: entity,
55+ state: root.srv.state,
56+ rpcConn: root.rpcConn,
57+ resources: common.NewResources(),
58+ entity: entity,
59+ objectCache: make(map[facadeKey]interface{}),
60 }
61 // Note: Only the Client API is part of srvRoot rather than all
62 // the other ones being created on-demand. Why is that?
63@@ -69,22 +76,31 @@
64 // that has a Call() method that will invoke the concrete method on a live
65 // object.
66 func (r *srvRoot) MethodCaller(rootMethodName string, version int, objId, objMethodName string) (rpcreflect.MethodCaller, error) {
67- factory, err := common.GetFacadeFactory(rootMethodName, version)
68- if err != nil {
69- return rpcreflect.MethodCaller{}, err
70- }
71- expectedType, err := common.GetFacadeType(rootMethodName, version)
72- if err != nil {
73- return rpcreflect.MethodCaller{}, err
74- }
75- facade, err := factory(r.state, r.resources, r, objId)
76- if err != nil {
77- return rpcreflect.MethodCaller{}, err
78- }
79- if !reflect.TypeOf(facade).AssignableTo(expectedType) {
80- return rpcreflect.MethodCaller{}, fmt.Errorf(
81- "internal error, %s(%d) claimed to return %s but returned %T",
82- rootMethodName, version, expectedType, facade)
83+ key := facadeKey{rootMethodName, version}
84+ var facade interface{}
85+ if obj, ok := r.objectCache[key]; ok {
86+ logger.Tracef("cache hit for %s(%d)", rootMethodName, version)
87+ facade = obj
88+ } else {
89+ logger.Tracef("cache miss for %s(%d)", rootMethodName, version)
90+ factory, err := common.GetFacadeFactory(rootMethodName, version)
91+ if err != nil {
92+ return rpcreflect.MethodCaller{}, err
93+ }
94+ expectedType, err := common.GetFacadeType(rootMethodName, version)
95+ if err != nil {
96+ return rpcreflect.MethodCaller{}, err
97+ }
98+ facade, err = factory(r.state, r.resources, r, objId)
99+ if err != nil {
100+ return rpcreflect.MethodCaller{}, err
101+ }
102+ if !reflect.TypeOf(facade).AssignableTo(expectedType) {
103+ return rpcreflect.MethodCaller{}, fmt.Errorf(
104+ "internal error, %s(%d) claimed to return %s but returned %T",
105+ rootMethodName, version, expectedType, facade)
106+ }
107+ r.objectCache[key] = facade
108 }
109 return rpcreflect.MethodCallerFromValue(
110 reflect.ValueOf(facade), rootMethodName, version, objMethodName)
111
112=== modified file 'state/apiserver/root_test.go'
113--- state/apiserver/root_test.go 2014-05-26 10:15:11 +0000
114+++ state/apiserver/root_test.go 2014-05-26 10:15:12 +0000
115@@ -105,3 +105,63 @@
116 c.Check(err, gc.ErrorMatches,
117 `internal error, my-testing-facade\(0\) claimed to return \*apiserver_test.testingType but returned \*apiserver_test.badType`)
118 }
119+
120+type intVar struct {
121+ I int64
122+}
123+
124+type countingType struct {
125+ i int64
126+}
127+
128+func (ct *countingType) Count() intVar {
129+ return intVar{ct.i}
130+}
131+
132+func (ct *countingType) AltCount() intVar {
133+ return intVar{ct.i}
134+}
135+
136+func (r *rootSuite) TestMethodCallerCachesFacades(c *gc.C) {
137+ srvRoot := apiserver.TestingSrvRoot(nil)
138+ defer common.Facades.Discard("my-counting-facade", 0)
139+ defer common.Facades.Discard("my-counting-facade", 1)
140+ var count int64
141+ newCounter := func(
142+ *state.State, *common.Resources, common.Authorizer,
143+ ) (
144+ *countingType, error,
145+ ) {
146+ count += 1
147+ return &countingType{count}, nil
148+ }
149+ common.RegisterStandardFacade("my-counting-facade", 0, newCounter)
150+ common.RegisterStandardFacade("my-counting-facade", 1, newCounter)
151+ // The first time we call MethodCaller, it should lookup a facade, and
152+ // request a new object.
153+ caller, err := srvRoot.MethodCaller("my-counting-facade", 0, "", "Count")
154+ c.Assert(err, gc.IsNil)
155+ v, err := caller.Call(reflect.Value{})
156+ c.Assert(err, gc.IsNil)
157+ c.Check(v.Interface(), gc.Equals, intVar{1})
158+ // The second time we ask for a method on the same facade, it should
159+ // reuse that object, rather than creating another instance
160+ caller, err = srvRoot.MethodCaller("my-counting-facade", 0, "", "AltCount")
161+ c.Assert(err, gc.IsNil)
162+ v, err = caller.Call(reflect.Value{})
163+ c.Assert(err, gc.IsNil)
164+ c.Check(v.Interface(), gc.Equals, intVar{1})
165+ // But when we ask for a different version, we should get a new
166+ // instance
167+ caller, err = srvRoot.MethodCaller("my-counting-facade", 1, "", "Count")
168+ c.Assert(err, gc.IsNil)
169+ v, err = caller.Call(reflect.Value{})
170+ c.Assert(err, gc.IsNil)
171+ c.Check(v.Interface(), gc.Equals, intVar{2})
172+ // But it, too, should be cached
173+ caller, err = srvRoot.MethodCaller("my-counting-facade", 1, "", "AltCount")
174+ c.Assert(err, gc.IsNil)
175+ v, err = caller.Call(reflect.Value{})
176+ c.Assert(err, gc.IsNil)
177+ c.Check(v.Interface(), gc.Equals, intVar{2})
178+}

Subscribers

People subscribed via source and target branches

to status/vote changes: