Skip to content

Commit a83ed6a

Browse files
author
hideki
committed
Issue: Reduce intermittently returns wrong results for 1.3.0-45 As posted in https://github.com/couchbase/couchbase-lite-java-core/issues/1375#issuecomment-235748848, given keys and values are correct, but result from reduce function is incorrect. According to Rhino documentation (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scopes_and_Contexts), Scope is sharable from multiple threads, but others are not. CBL Java/Android used to compile given JavaScript for every requests. But it makes JavaScript map/reduce function makes really slow. So we switched to "compile once, and reuse it" style. It seems accessing `Function` instance from multiple threads is not safe. So, by this fix, synchronized execution block.
1 parent c5d557d commit a83ed6a

File tree

3 files changed

+127
-105
lines changed

3 files changed

+127
-105
lines changed
Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
/**
2-
* Copyright (c) 2015 Couchbase, Inc All rights reserved.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5-
* except in compliance with the License. You may obtain a copy of the License at
6-
*
7-
* http://www.apache.org/licenses/LICENSE-2.0
8-
*
9-
* Unless required by applicable law or agreed to in writing, software distributed under the
10-
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11-
* either express or implied. See the License for the specific language governing permissions
12-
* and limitations under the License.
13-
*/
14-
1+
//
2+
// Copyright (c) 2016 Couchbase, Inc. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
// except in compliance with the License. You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software distributed under the
10+
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11+
// either express or implied. See the License for the specific language governing permissions
12+
// and limitations under the License.
13+
//
1514
package com.couchbase.lite.javascript;
1615

1716
import com.couchbase.lite.ReplicationFilter;
@@ -30,14 +29,21 @@
3029
* Created by hideki on 10/28/15.
3130
*/
3231
public class ReplicationFilterBlockRhino implements ReplicationFilter {
33-
public static String TAG = "ReplicationFilterBlockRhino";
32+
public static String TAG = "JavaScriptEngine";
3433

3534
private static WrapFactory wrapFactory = new CustomWrapFactory();
3635
private Scriptable scope;
3736
private GlobalScope globalScope;
3837
private Function filterFunction;
3938

40-
public ReplicationFilterBlockRhino(String src){
39+
// NOTE: Scope is sharable with multiple threads, it seems `Function` is not.
40+
// Compiling javascript codes for every request makes slow.
41+
// It is reason current code base re-use compiled Function.
42+
// Instead of compiling for every request, use `synchronized` to protect Function
43+
// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scopes_and_Contexts
44+
private final Object lockFunction = new Object();
45+
46+
public ReplicationFilterBlockRhino(String src) {
4147
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
4248
try {
4349
ctx.setOptimizationLevel(-1);
@@ -52,28 +58,27 @@ public ReplicationFilterBlockRhino(String src){
5258

5359
@Override
5460
public boolean filter(SavedRevision revision, Map<String, Object> params) {
55-
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
56-
try {
57-
ctx.setOptimizationLevel(-1);
58-
ctx.setWrapFactory(wrapFactory);
59-
60-
Scriptable localScope = ctx.newObject(scope);
61-
localScope.setPrototype(scope);
62-
localScope.setParentScope(null);
63-
64-
Object jsDocument = org.mozilla.javascript.Context.javaToJS(revision.getProperties(), localScope);
65-
Object jsParams = org.mozilla.javascript.Context.javaToJS(params, localScope);
66-
61+
synchronized (lockFunction) {
62+
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
6763
try {
68-
Object result = filterFunction.call(ctx, localScope, null, new Object[]{jsDocument, jsParams});
69-
return ((Boolean)result).booleanValue();
70-
} catch (org.mozilla.javascript.RhinoException e) {
71-
// Error in the JavaScript view - CouchDB swallows the error and tries the next document
72-
Log.e(TAG, "Error in filterFunction.call()", e);
73-
return false;
64+
ctx.setOptimizationLevel(-1);
65+
ctx.setWrapFactory(wrapFactory);
66+
Scriptable localScope = ctx.newObject(scope);
67+
localScope.setPrototype(scope);
68+
localScope.setParentScope(null);
69+
Object jsDocument = org.mozilla.javascript.Context.javaToJS(revision.getProperties(), localScope);
70+
Object jsParams = org.mozilla.javascript.Context.javaToJS(params, localScope);
71+
72+
try {
73+
Object result = filterFunction.call(ctx, localScope, null, new Object[]{jsDocument, jsParams});
74+
return ((Boolean) result).booleanValue();
75+
} catch (org.mozilla.javascript.RhinoException e) {
76+
Log.e(TAG, "Error in filterFunction.call()", e);
77+
return false;
78+
}
79+
} finally {
80+
org.mozilla.javascript.Context.exit();
7481
}
75-
} finally {
76-
org.mozilla.javascript.Context.exit();
7782
}
7883
}
7984
}
Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
/**
2-
* Copyright (c) 2015 Couchbase, Inc All rights reserved.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5-
* except in compliance with the License. You may obtain a copy of the License at
6-
*
7-
* http://www.apache.org/licenses/LICENSE-2.0
8-
*
9-
* Unless required by applicable law or agreed to in writing, software distributed under the
10-
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11-
* either express or implied. See the License for the specific language governing permissions
12-
* and limitations under the License.
13-
*/
1+
//
2+
// Copyright (c) 2016 Couchbase, Inc. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
// except in compliance with the License. You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software distributed under the
10+
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11+
// either express or implied. See the License for the specific language governing permissions
12+
// and limitations under the License.
13+
//
1414
package com.couchbase.lite.javascript;
1515

1616
import com.couchbase.lite.Emitter;
1717
import com.couchbase.lite.Mapper;
1818
import com.couchbase.lite.javascript.scopes.MapGlobalScope;
1919
import com.couchbase.lite.javascript.wrapper.CustomWrapFactory;
20+
import com.couchbase.lite.util.Log;
2021

2122
import org.mozilla.javascript.Function;
2223
import org.mozilla.javascript.Scriptable;
@@ -25,16 +26,22 @@
2526
import java.util.Map;
2627

2728
class ViewMapBlockRhino implements Mapper {
29+
private final static String TAG = "JavaScriptEngine";
2830

2931
private static WrapFactory wrapFactory = new CustomWrapFactory();
3032
private Scriptable globalScope;
3133
private MapGlobalScope mapGlobalScope;
3234
private Function mapFunction;
3335

34-
public ViewMapBlockRhino(String src) {
36+
// NOTE: Scope is sharable with multiple threads, it seems `Function` is not.
37+
// Compiling javascript codes for every request makes slow.
38+
// It is reason current code base re-use compiled Function.
39+
// Instead of compiling for every request, use `synchronized` to protect Function
40+
// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scopes_and_Contexts
41+
private final Object lockFunction = new Object();
3542

43+
public ViewMapBlockRhino(String src) {
3644
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
37-
3845
try {
3946
ctx.setOptimizationLevel(-1);
4047
ctx.setWrapFactory(wrapFactory);
@@ -48,28 +55,29 @@ public ViewMapBlockRhino(String src) {
4855

4956
@Override
5057
public void map(Map<String, Object> document, Emitter emitter) {
58+
synchronized (lockFunction) {
59+
mapGlobalScope.setEmitter(emitter);
5160

52-
mapGlobalScope.setEmitter(emitter);
53-
54-
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
55-
try {
56-
ctx.setOptimizationLevel(-1);
57-
ctx.setWrapFactory(wrapFactory);
61+
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
62+
try {
63+
ctx.setOptimizationLevel(-1);
64+
ctx.setWrapFactory(wrapFactory);
5865

59-
Scriptable localScope = ctx.newObject(globalScope);
60-
localScope.setPrototype(globalScope);
61-
localScope.setParentScope(null);
66+
Scriptable localScope = ctx.newObject(globalScope);
67+
localScope.setPrototype(globalScope);
68+
localScope.setParentScope(null);
6269

63-
Object jsDocument = org.mozilla.javascript.Context.javaToJS(document, localScope);
70+
Object jsDocument = org.mozilla.javascript.Context.javaToJS(document, localScope);
6471

65-
try {
66-
mapFunction.call(ctx, localScope, null, new Object[]{jsDocument});
67-
} catch (org.mozilla.javascript.RhinoException e) {
68-
// Error in the JavaScript view - CouchDB swallows the error and tries the next document
69-
return;
72+
try {
73+
mapFunction.call(ctx, localScope, null, new Object[]{jsDocument});
74+
} catch (org.mozilla.javascript.RhinoException e) {
75+
Log.e(TAG, "Error in calling JavaScript map function", e);
76+
return;
77+
}
78+
} finally {
79+
org.mozilla.javascript.Context.exit();
7080
}
71-
} finally {
72-
org.mozilla.javascript.Context.exit();
7381
}
7482
}
7583
}

src/main/java/com/couchbase/lite/javascript/ViewReduceBlockRhino.java

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
/**
2-
* Copyright (c) 2015 Couchbase, Inc All rights reserved.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5-
* except in compliance with the License. You may obtain a copy of the License at
6-
*
7-
* http://www.apache.org/licenses/LICENSE-2.0
8-
*
9-
* Unless required by applicable law or agreed to in writing, software distributed under the
10-
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11-
* either express or implied. See the License for the specific language governing permissions
12-
* and limitations under the License.
13-
*/
1+
//
2+
// Copyright (c) 2016 Couchbase, Inc. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
// except in compliance with the License. You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software distributed under the
10+
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11+
// either express or implied. See the License for the specific language governing permissions
12+
// and limitations under the License.
13+
//
1414
package com.couchbase.lite.javascript;
1515

1616
import com.couchbase.lite.Reducer;
1717
import com.couchbase.lite.javascript.scopes.ReduceGlobalScope;
1818
import com.couchbase.lite.javascript.wrapper.CustomWrapFactory;
19+
import com.couchbase.lite.util.Log;
1920

2021
import org.mozilla.javascript.Function;
2122
import org.mozilla.javascript.Scriptable;
@@ -25,20 +26,27 @@
2526
import java.util.List;
2627

2728
class ViewReduceBlockRhino implements Reducer {
29+
private final static String TAG = "JavaScriptEngine";
2830

2931
private static WrapFactory wrapFactory = new CustomWrapFactory();
3032
private final ReduceGlobalScope reduceGlobalScope;
3133
private final ScriptableObject globalScope;
3234
private final Function reduceFunction;
3335
private final NativReduceFunctions nativeReduce;
3436

37+
// NOTE: Scope is sharable with multiple threads, it seems `Function` is not.
38+
// Compiling javascript codes for every request makes slow.
39+
// It is reason current code base re-use compiled Function.
40+
// Instead of compiling for every request, use `synchronized` to protect Function
41+
// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scopes_and_Contexts
42+
private final Object lockFunction = new Object();
43+
3544
public ViewReduceBlockRhino(String src) {
3645

3746
nativeReduce = NativReduceFunctions.fromKey(src);
3847

3948
if (nativeReduce == NativReduceFunctions.DEFAULT) {
4049
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
41-
4250
try {
4351
ctx.setOptimizationLevel(-1);
4452
ctx.setWrapFactory(wrapFactory);
@@ -58,36 +66,37 @@ public ViewReduceBlockRhino(String src) {
5866

5967
@Override
6068
public Object reduce(List<Object> keys, List<Object> values, boolean reReduce) {
61-
6269
switch (nativeReduce) {
6370
case SUM:
6471
return nativeSum(keys, values, reReduce);
6572
case COUNT:
6673
return nativeCount(keys, values, reReduce);
6774
case DEFAULT:
6875
default:
69-
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
70-
try {
71-
ctx.setOptimizationLevel(-1);
72-
ctx.setWrapFactory(wrapFactory);
73-
74-
Scriptable localScope = ctx.newObject(globalScope);
75-
localScope.setPrototype(globalScope);
76-
localScope.setParentScope(null);
77-
78-
Object[] args = new Object[3];
79-
80-
args[0] = org.mozilla.javascript.Context.javaToJS(keys, localScope);
81-
args[1] = org.mozilla.javascript.Context.javaToJS(values, localScope);
82-
args[2] = org.mozilla.javascript.Context.javaToJS(reReduce, localScope);
83-
84-
return reduceFunction.call(ctx, localScope, null, args);
85-
86-
} catch (org.mozilla.javascript.RhinoException e) {
87-
// TODO check couchdb behaviour on error in reduce function
88-
return null;
89-
} finally {
90-
org.mozilla.javascript.Context.exit();
76+
synchronized (lockFunction) {
77+
org.mozilla.javascript.Context ctx = org.mozilla.javascript.Context.enter();
78+
try {
79+
ctx.setOptimizationLevel(-1);
80+
ctx.setWrapFactory(wrapFactory);
81+
82+
Scriptable localScope = ctx.newObject(globalScope);
83+
localScope.setPrototype(globalScope);
84+
localScope.setParentScope(null);
85+
86+
Object[] args = new Object[3];
87+
args[0] = org.mozilla.javascript.Context.javaToJS(keys, localScope);
88+
args[1] = org.mozilla.javascript.Context.javaToJS(values, localScope);
89+
args[2] = org.mozilla.javascript.Context.javaToJS(reReduce, localScope);
90+
91+
try {
92+
return reduceFunction.call(ctx, localScope, null, args);
93+
} catch (org.mozilla.javascript.RhinoException e) {
94+
Log.e(TAG, "Error in calling JavaScript reduce function", e);
95+
return null;
96+
}
97+
} finally {
98+
org.mozilla.javascript.Context.exit();
99+
}
91100
}
92101
}
93102
}

0 commit comments

Comments
 (0)