Skip to content

Commit 23797b4

Browse files
authored
Merge pull request #83 from natefaubion/peekVar-fix
peekVar fix
2 parents 7ee0914 + 6799f00 commit 23797b4

File tree

4 files changed

+139
-114
lines changed

4 files changed

+139
-114
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
"private": true,
33
"scripts": {
44
"clean": "rimraf output && rimraf .pulp-cache",
5-
"build": "jshint src && jscs src && pulp build --censor-lib --strict",
5+
"build": "jshint src && jscs src && pulp build -- --censor-lib --strict",
66
"test": "pulp test"
77
},
88
"devDependencies": {
99
"jscs": "^3.0.7",
1010
"jshint": "^2.9.4",
11-
"pulp": "^9.0.1",
12-
"purescript-psa": "^0.3.9",
11+
"pulp": "^10.0.0",
12+
"purescript-psa": "^0.4.0",
1313
"purescript": "^0.10.1",
1414
"rimraf": "^2.5.4"
1515
}

src/Control/Monad/Aff.js

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,13 @@ exports._forkAff = function (nonCanceler, aff) {
8181
exports._forkAll = function (nonCanceler, foldl, affs) {
8282
var voidF = function () {};
8383

84-
return function (success, error) {
85-
try {
86-
var cancelers = foldl(function (acc) {
87-
return function (aff) {
88-
acc.push(aff(voidF, voidF));
89-
return acc;
90-
};
91-
})([])(affs);
92-
} catch (err) {
93-
error(err);
94-
}
84+
return function (success) {
85+
var cancelers = foldl(function (acc) {
86+
return function (aff) {
87+
acc.push(aff(voidF, voidF));
88+
return acc;
89+
};
90+
})([])(affs);
9591

9692
var canceler = function (e) {
9793
return function (success, error) {
@@ -162,18 +158,9 @@ exports._throwError = function (nonCanceler, e) {
162158

163159
exports._fmap = function (f, aff) {
164160
return function (success, error) {
165-
try {
166-
return aff(function (v) {
167-
try {
168-
var v2 = f(v);
169-
} catch (err) {
170-
error(err);
171-
}
172-
success(v2);
173-
}, error);
174-
} catch (err) {
175-
error(err);
176-
}
161+
return aff(function (v) {
162+
success(f(v));
163+
}, error);
177164
};
178165
};
179166

@@ -224,25 +211,54 @@ exports._bind = function (alwaysCanceler, aff, f) {
224211

225212
exports._attempt = function (Left, Right, aff) {
226213
return function (success) {
227-
try {
228-
return aff(function (v) {
229-
success(Right(v));
230-
}, function (e) {
231-
success(Left(e));
232-
});
233-
} catch (err) {
234-
success(Left(err));
235-
}
214+
return aff(function (v) {
215+
success(Right(v));
216+
}, function (e) {
217+
success(Left(e));
218+
});
236219
};
237220
};
238221

239222
exports._runAff = function (errorT, successT, aff) {
223+
// If errorT or successT throw, and an Aff is comprised only of synchronous
224+
// effects, then it's possible for makeAff/liftEff to accidentally catch
225+
// it, which may end up rerunning the Aff depending on error recovery
226+
// behavior. To mitigate this, we observe synchronicity using mutation. If
227+
// an Aff is observed to be synchronous, we let the stack reset and run the
228+
// handlers outside of the normal callback flow.
240229
return function () {
241-
return aff(function (v) {
242-
successT(v)();
230+
var status = 0;
231+
var result, success;
232+
233+
var canceler = aff(function (v) {
234+
if (status === 2) {
235+
successT(v)();
236+
} else {
237+
status = 1;
238+
result = v;
239+
success = true;
240+
}
243241
}, function (e) {
244-
errorT(e)();
242+
if (status === 2) {
243+
errorT(e)();
244+
} else {
245+
status = 1;
246+
result = e;
247+
success = false;
248+
}
245249
});
250+
251+
if (status === 1) {
252+
if (success) {
253+
successT(result)();
254+
} else {
255+
errorT(result)();
256+
}
257+
} else {
258+
status = 2;
259+
}
260+
261+
return canceler;
246262
};
247263
};
248264

@@ -284,11 +300,7 @@ exports._tailRecM = function (isLeft, f, a) {
284300
if (isLeft(v)) {
285301
go(v.value0);
286302
} else {
287-
try {
288-
success(v.value0);
289-
} catch (err) {
290-
error(err);
291-
}
303+
success(v.value0);
292304
}
293305
}
294306
};
@@ -307,11 +319,7 @@ exports._tailRecM = function (isLeft, f, a) {
307319
acc = result.value0;
308320
continue;
309321
} else {
310-
try {
311-
success(result.value0);
312-
} catch (err) {
313-
error(err);
314-
}
322+
success(result.value0);
315323
}
316324
} else {
317325
// If the status has not resolved yet, then we have observed an

src/Control/Monad/Aff/Internal.js

Lines changed: 36 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
"use strict";
22

33
exports._makeVar = function (nonCanceler) {
4-
return function (success, error) {
5-
try {
6-
success({
7-
consumers: [],
8-
producers: [],
9-
error: undefined
10-
});
11-
} catch (err) {
12-
error(err);
13-
}
14-
4+
return function (success) {
5+
success({
6+
consumers: [],
7+
producers: [],
8+
error: undefined
9+
});
1510
return nonCanceler;
1611
};
1712
};
@@ -21,11 +16,9 @@ exports._takeVar = function (nonCanceler, avar) {
2116
if (avar.error !== undefined) {
2217
error(avar.error);
2318
} else if (avar.producers.length > 0) {
24-
var producer = avar.producers.shift();
25-
26-
producer(success, error);
19+
avar.producers.shift()(success, error);
2720
} else {
28-
avar.consumers.push({ success: success, error: error });
21+
avar.consumers.push({ peek: false, success: success, error: error });
2922
}
3023

3124
return nonCanceler;
@@ -37,8 +30,7 @@ exports._peekVar = function (nonCanceler, avar) {
3730
if (avar.error !== undefined) {
3831
error(avar.error);
3932
} else if (avar.producers.length > 0) {
40-
var producer = avar.producers[0];
41-
producer(success, error);
33+
avar.producers[0](success, error);
4234
} else {
4335
avar.consumers.push({ peek: true, success: success, error: error });
4436
}
@@ -50,28 +42,34 @@ exports._putVar = function (nonCanceler, avar, a) {
5042
return function (success, error) {
5143
if (avar.error !== undefined) {
5244
error(avar.error);
53-
} else if (avar.consumers.length === 0) {
54-
avar.producers.push(function (success, error) {
55-
try {
56-
success(a);
57-
} catch (err) {
58-
error(err);
59-
}
60-
});
61-
62-
success({});
6345
} else {
64-
46+
var shouldQueue = true;
47+
var consumers = [];
6548
var consumer;
66-
do {
49+
50+
while (true) {
6751
consumer = avar.consumers.shift();
68-
try {
69-
consumer.success(a);
70-
} catch (err) {
71-
error(err);
72-
return;
52+
if (consumer) {
53+
consumers.push(consumer);
54+
if (consumer.peek) {
55+
continue;
56+
} else {
57+
shouldQueue = false;
58+
}
7359
}
74-
} while (consumer.peek === true);
60+
break;
61+
}
62+
63+
if (shouldQueue) {
64+
avar.producers.push(function (success) {
65+
success(a);
66+
return nonCanceler;
67+
});
68+
}
69+
70+
for (var i = 0; i < consumers.length; i++) {
71+
consumers[i].success(a);
72+
}
7573

7674
success({});
7775
}
@@ -85,22 +83,11 @@ exports._killVar = function (nonCanceler, avar, e) {
8583
if (avar.error !== undefined) {
8684
error(avar.error);
8785
} else {
88-
var errors = [];
89-
9086
avar.error = e;
91-
92-
while (avar.consumers.length > 0) {
93-
var consumer = avar.consumers.shift();
94-
95-
try {
96-
consumer.error(e);
97-
} catch (err) {
98-
errors.push(err);
99-
}
87+
while (avar.consumers.length) {
88+
avar.consumers.shift().error(e);
10089
}
101-
102-
if (errors.length > 0) error(errors[0]);
103-
else success({});
90+
success({});
10491
}
10592

10693
return nonCanceler;

test/Test/Main.purs

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,24 @@ import Control.Monad.Error.Class (throwError)
1313
import Control.Monad.Rec.Class (Step(..), tailRecM)
1414
import Control.Parallel (parallel, sequential)
1515
import Data.Either (either, fromLeft, fromRight)
16+
import Data.Maybe (Maybe(..))
1617
import Data.Unfoldable (replicate)
1718
import Partial.Unsafe (unsafePartial)
1819

1920
type Test a = forall e. Aff (console :: CONSOLE | e) a
2021
type TestAVar a = forall e. Aff (console :: CONSOLE, avar :: AVAR | e) a
2122

23+
timeout :: Int TestAVar Unit TestAVar Unit
24+
timeout ms aff = do
25+
exn <- makeVar
26+
clr1 <- forkAff (later' ms (putVar exn (Just "Timed out")))
27+
clr2 <- forkAff (aff *> putVar exn Nothing)
28+
res ← takeVar exn
29+
log (show res)
30+
case res of
31+
Nothing -> void (clr1 `cancel` error "Done")
32+
Just e -> void (clr2 `cancel` error "Done") *> throwError (error e)
33+
2234
replicateArray :: forall a. Int -> a -> Array a
2335
replicateArray = replicate
2436

@@ -69,20 +81,38 @@ test_putTakeVar = do
6981

7082
test_peekVar :: TestAVar Unit
7183
test_peekVar = do
72-
v <- makeVar
73-
forkAff (later $ putVar v 1.0)
74-
a1 <- peekVar v
75-
a2 <- takeVar v
76-
when (a1 /= a2) do
77-
throwError (error "Something horrible went wrong - peeked var is not equal to taken var")
78-
log ("Success: Peeked value not consumed")
79-
80-
w <- makeVar
81-
putVar w true
82-
b <- peekVar w
83-
when (not b) do
84-
throwError (error "Something horrible went wrong - peeked var is not true")
85-
log ("Success: Peeked value read from written var")
84+
timeout 1000 do
85+
v <- makeVar
86+
forkAff (later $ putVar v 1.0)
87+
a1 <- peekVar v
88+
a2 <- takeVar v
89+
when (a1 /= a2) do
90+
throwError (error "Something horrible went wrong - peeked var is not equal to taken var")
91+
log ("Success: Peeked value not consumed")
92+
93+
timeout 1000 do
94+
w <- makeVar
95+
putVar w true
96+
b <- peekVar w
97+
when (not b) do
98+
throwError (error "Something horrible went wrong - peeked var is not true")
99+
log ("Success: Peeked value read from written var")
100+
101+
timeout 1000 do
102+
x <- makeVar
103+
res <- makeVar' 1
104+
forkAff do
105+
c <- peekVar x
106+
putVar x 1000
107+
d <- peekVar x
108+
modifyVar (_ + (c + d)) res
109+
putVar x 10
110+
count <- takeVar res
111+
e <- takeVar x
112+
f <- takeVar x
113+
when (not (count == 21 && e == 10 && f == 1000)) do
114+
throwError (error "Something horrible went wrong - peeked consumers/producer ordering")
115+
log "Success: peekVar consumer/producer order maintained"
86116

87117
test_killFirstForked :: Test Unit
88118
test_killFirstForked = do
@@ -191,10 +221,10 @@ loopAndBounce n = do
191221
log $ "Done: " <> show res
192222
where
193223
go 0 = pure (Done 0)
194-
go n | mod n 30000 == 0 = do
224+
go k | mod k 30000 == 0 = do
195225
later' 10 (pure unit)
196-
pure (Loop (n - 1))
197-
go n = pure (Loop (n - 1))
226+
pure (Loop (k - 1))
227+
go k = pure (Loop (k - 1))
198228

199229
all :: forall eff. Int -> Aff (console :: CONSOLE, avar :: AVAR | eff) Unit
200230
all n = do

0 commit comments

Comments
 (0)