@@ -1002,119 +1002,130 @@ class UpdateMachine {
1002
1002
1003
1003
void poll () async {
1004
1004
assert (! _disposed);
1005
-
1006
- BackoffMachine ? backoffMachine;
1007
- int accumulatedTransientFailureCount = 0 ;
1008
-
1009
- /// This only reports transient errors after reaching
1010
- /// a pre-defined threshold of retries.
1011
- void maybeReportToUserTransientError (Object error) {
1012
- accumulatedTransientFailureCount++ ;
1013
- if (accumulatedTransientFailureCount > transientFailureCountNotifyThreshold) {
1014
- _reportToUserErrorConnectingToServer (error);
1015
- }
1016
- }
1017
-
1018
- while (true ) {
1019
- if (_debugLoopSignal != null ) {
1020
- await _debugLoopSignal! .future;
1021
- if (_disposed) return ;
1022
- assert (() {
1023
- _debugLoopSignal = Completer ();
1024
- return true ;
1025
- }());
1005
+ try {
1006
+ BackoffMachine ? backoffMachine;
1007
+ int accumulatedTransientFailureCount = 0 ;
1008
+
1009
+ /// This only reports transient errors after reaching
1010
+ /// a pre-defined threshold of retries.
1011
+ void maybeReportToUserTransientError (Object error) {
1012
+ accumulatedTransientFailureCount++ ;
1013
+ if (accumulatedTransientFailureCount > transientFailureCountNotifyThreshold) {
1014
+ _reportToUserErrorConnectingToServer (error);
1015
+ }
1026
1016
}
1027
1017
1028
- final GetEventsResult result;
1029
- try {
1030
- result = await getEvents (store.connection,
1031
- queueId: queueId, lastEventId: lastEventId);
1032
- if (_disposed) return ;
1033
- } catch (e) {
1034
- if (_disposed) return ;
1035
-
1036
- store.isLoading = true ;
1037
- bool isUnexpected;
1038
- bool shouldReportToUser;
1039
- switch (e) {
1040
- case ZulipApiException (code: 'BAD_EVENT_QUEUE_ID' ):
1041
- assert (debugLog ('Lost event queue for $store . Replacing…' ));
1042
- // This disposes the store, which disposes this update machine.
1043
- await store._globalStore._reloadPerAccount (store.accountId);
1044
- assert (debugLog ('… Event queue replaced.' ));
1045
- return ;
1046
-
1047
- case NetworkException (cause: SocketException ()):
1048
- // A [SocketException] is common when the app returns from sleep.
1049
- isUnexpected = false ;
1050
- shouldReportToUser = false ;
1051
-
1052
- case NetworkException ():
1053
- case Server5xxException ():
1054
- isUnexpected = false ;
1055
- shouldReportToUser = true ;
1056
-
1057
- case ServerException (httpStatus: 429 ):
1058
- case ZulipApiException (httpStatus: 429 ):
1059
- case ZulipApiException (code: 'RATE_LIMIT_HIT' ):
1060
- // TODO(#946) handle rate-limit errors more generally, in ApiConnection
1061
- isUnexpected = false ;
1062
- shouldReportToUser = true ;
1063
-
1064
- default :
1065
- isUnexpected = true ;
1066
- shouldReportToUser = true ;
1018
+ while (true ) {
1019
+ if (_debugLoopSignal != null ) {
1020
+ await _debugLoopSignal! .future;
1021
+ if (_disposed) return ;
1022
+ assert (() {
1023
+ _debugLoopSignal = Completer ();
1024
+ return true ;
1025
+ }());
1067
1026
}
1068
1027
1069
- if (isUnexpected) {
1070
- assert (shouldReportToUser);
1071
- assert (debugLog ('Error polling event queue for $store : $e \n '
1072
- 'Backing off and retrying even though may be hopeless…' ));
1073
- // TODO(#186): Handle unrecoverable failures
1074
- _reportToUserErrorConnectingToServer (e);
1075
- } else {
1076
- assert (debugLog ('Transient error polling event queue for $store : $e \n '
1077
- 'Backing off, then will retry…' ));
1078
- if (shouldReportToUser) {
1079
- maybeReportToUserTransientError (e);
1028
+ final GetEventsResult result;
1029
+ try {
1030
+ result = await getEvents (store.connection,
1031
+ queueId: queueId, lastEventId: lastEventId);
1032
+ if (_disposed) return ;
1033
+ } catch (e) {
1034
+ if (_disposed) return ;
1035
+
1036
+ store.isLoading = true ;
1037
+ bool isUnexpected;
1038
+ bool shouldReportToUser;
1039
+ switch (e) {
1040
+ case ZulipApiException (code: 'BAD_EVENT_QUEUE_ID' ):
1041
+ rethrow ;
1042
+
1043
+ case NetworkException (cause: SocketException ()):
1044
+ // A [SocketException] is common when the app returns from sleep.
1045
+ isUnexpected = false ;
1046
+ shouldReportToUser = false ;
1047
+
1048
+ case NetworkException ():
1049
+ case Server5xxException ():
1050
+ isUnexpected = false ;
1051
+ shouldReportToUser = true ;
1052
+
1053
+ case ServerException (httpStatus: 429 ):
1054
+ case ZulipApiException (httpStatus: 429 ):
1055
+ case ZulipApiException (code: 'RATE_LIMIT_HIT' ):
1056
+ // TODO(#946) handle rate-limit errors more generally, in ApiConnection
1057
+ isUnexpected = false ;
1058
+ shouldReportToUser = true ;
1059
+
1060
+ default :
1061
+ isUnexpected = true ;
1062
+ shouldReportToUser = true ;
1080
1063
}
1064
+
1065
+ if (isUnexpected) {
1066
+ assert (shouldReportToUser);
1067
+ assert (debugLog ('Error polling event queue for $store : $e \n '
1068
+ 'Backing off and retrying even though may be hopeless…' ));
1069
+ // TODO(#186): Handle unrecoverable failures
1070
+ _reportToUserErrorConnectingToServer (e);
1071
+ } else {
1072
+ assert (debugLog ('Transient error polling event queue for $store : $e \n '
1073
+ 'Backing off, then will retry…' ));
1074
+ if (shouldReportToUser) {
1075
+ maybeReportToUserTransientError (e);
1076
+ }
1077
+ }
1078
+ await (backoffMachine ?? = BackoffMachine ()).wait ();
1079
+ if (_disposed) return ;
1080
+ assert (debugLog ('… Backoff wait complete, retrying poll.' ));
1081
+ continue ;
1081
1082
}
1082
- await (backoffMachine ?? = BackoffMachine ()).wait ();
1083
- if (_disposed) return ;
1084
- assert (debugLog ('… Backoff wait complete, retrying poll.' ));
1085
- continue ;
1086
- }
1087
1083
1088
- // After one successful request, we reset backoff to its initial state.
1089
- // That way if the user is off the network and comes back on, the app
1090
- // doesn't wind up in a state where it's slow to recover the next time
1091
- // one request fails.
1092
- //
1093
- // This does mean that if the server is having trouble and handling some
1094
- // but not all of its requests, we'll end up doing a lot more retries than
1095
- // if we stayed at the max backoff interval; partway toward what would
1096
- // happen if we weren't backing off at all.
1097
- //
1098
- // But at least for [getEvents] requests, as here, it should be OK,
1099
- // because this is a long-poll. That means a typical successful request
1100
- // takes a long time to come back; in fact longer than our max backoff
1101
- // duration (which is 10 seconds). So if we're getting a mix of successes
1102
- // and failures, the successes themselves should space out the requests.
1103
- backoffMachine = null ;
1104
-
1105
- store.isLoading = false ;
1106
- // Dismiss existing errors, if any.
1107
- reportErrorToUserBriefly (null );
1108
- accumulatedTransientFailureCount = 0 ;
1109
-
1110
- final events = result.events;
1111
- for (final event in events) {
1112
- await store.handleEvent (event);
1113
- if (_disposed) return ;
1084
+ // After one successful request, we reset backoff to its initial state.
1085
+ // That way if the user is off the network and comes back on, the app
1086
+ // doesn't wind up in a state where it's slow to recover the next time
1087
+ // one request fails.
1088
+ //
1089
+ // This does mean that if the server is having trouble and handling some
1090
+ // but not all of its requests, we'll end up doing a lot more retries than
1091
+ // if we stayed at the max backoff interval; partway toward what would
1092
+ // happen if we weren't backing off at all.
1093
+ //
1094
+ // But at least for [getEvents] requests, as here, it should be OK,
1095
+ // because this is a long-poll. That means a typical successful request
1096
+ // takes a long time to come back; in fact longer than our max backoff
1097
+ // duration (which is 10 seconds). So if we're getting a mix of successes
1098
+ // and failures, the successes themselves should space out the requests.
1099
+ backoffMachine = null ;
1100
+
1101
+ store.isLoading = false ;
1102
+ // Dismiss existing errors, if any.
1103
+ reportErrorToUserBriefly (null );
1104
+ accumulatedTransientFailureCount = 0 ;
1105
+
1106
+ final events = result.events;
1107
+ for (final event in events) {
1108
+ await store.handleEvent (event);
1109
+ if (_disposed) return ;
1110
+ }
1111
+ if (events.isNotEmpty) {
1112
+ lastEventId = events.last.id;
1113
+ }
1114
1114
}
1115
- if (events.isNotEmpty) {
1116
- lastEventId = events.last.id;
1115
+ } catch (e) {
1116
+ switch (e) {
1117
+ case ZulipApiException (code: 'BAD_EVENT_QUEUE_ID' ):
1118
+ assert (debugLog ('Lost event queue for $store . Replacing…' ));
1119
+ // The old event queue is gone, so we need a new one. This is normal.
1120
+
1121
+ default :
1122
+ rethrow ; // TODO(#563) try to recover instead
1117
1123
}
1124
+
1125
+ // This disposes the store, which disposes this update machine.
1126
+ await store._globalStore._reloadPerAccount (store.accountId);
1127
+ assert (debugLog ('… Event queue replaced.' ));
1128
+ return ;
1118
1129
}
1119
1130
}
1120
1131
0 commit comments