Skip to content

Latest commit

 

History

History
110 lines (85 loc) · 9.43 KB

File metadata and controls

110 lines (85 loc) · 9.43 KB

Mitigation Strategies Analysis for reactivex/rxdart

Mitigation Strategy: Using CompositeSubscription

Description:

  1. Create a CompositeSubscription: Instantiate a CompositeSubscription object (from the rxdart package) as a member of the class managing the subscriptions (e.g., a State object, a Bloc, or a ViewModel).
  2. Add Subscriptions: Instead of storing individual StreamSubscription variables, add each subscription to the CompositeSubscription using the add() method: compositeSubscription.add(stream.listen(...));. This method is part of the CompositeSubscription class.
  3. Dispose in dispose() (or equivalent): In the dispose() method (or the equivalent cleanup method of your state management solution), call dispose() on the CompositeSubscription object: compositeSubscription.dispose();. This method, provided by CompositeSubscription, cancels all subscriptions that have been added to it.

Threats Mitigated:

  • Memory Leaks: (Severity: High) - Prevents long-lived objects from being held in memory unnecessarily because subscriptions are not cancelled.
  • Performance Degradation: (Severity: Medium) - Reduces unnecessary processing and resource consumption caused by active, but unneeded, subscriptions.
  • Unexpected Behavior: (Severity: Medium) - Avoids situations where a disposed widget or component continues to react to stream events.
  • Application Crashes: (Severity: Critical) - In extreme cases of severe memory leaks, prevents out-of-memory crashes.

Impact:

  • Memory Leaks: Risk reduced significantly (almost eliminated if used consistently).
  • Performance Degradation: Risk reduced significantly.
  • Unexpected Behavior: Risk reduced significantly.
  • Application Crashes: Risk reduced significantly (related to memory leaks).

Currently Implemented:

  • Example: MyBloc uses a CompositeSubscription to manage all internal subscriptions, and dispose() is called in the Bloc's close() method.

Missing Implementation:

  • Example: SomeWidget manages multiple subscriptions individually within its State class, making the dispose() method more complex and prone to errors (forgetting to cancel one).

Mitigation Strategy: Using takeUntil Operator

Description:

  1. Create a "Dispose" Stream: Create a Stream that will emit a value when the component (e.g., Widget, Bloc) is disposed of. This is often a Subject (from rxdart), such as a PublishSubject<void>().
  2. Apply takeUntil: Use the takeUntil operator (provided by RxDart) on the stream you want to manage the lifecycle of. Pass the "dispose" stream as the argument to takeUntil. Example: myStream.takeUntil(_disposeSubject).
  3. Emit on Dispose: In the dispose() method of your StatefulWidget (or the equivalent cleanup method in your state management solution), emit a value on the "dispose" stream. For example: _disposeSubject.add(null);. This emission signals takeUntil to complete the original stream, automatically unsubscribing it.

Threats Mitigated:

  • Memory Leaks: (Severity: High)
  • Performance Degradation: (Severity: Medium)
  • Unexpected Behavior: (Severity: Medium)
  • Application Crashes: (Severity: Critical)

Impact:

  • Memory Leaks: Risk reduced significantly.
  • Performance Degradation: Risk reduced significantly.
  • Unexpected Behavior: Risk reduced significantly.
  • Application Crashes: Risk reduced significantly.

Currently Implemented:

  • Example: MyWidget uses a _disposeSubject (a PublishSubject) and takeUntil to manage the lifecycle of a stream subscription, emitting to _disposeSubject in dispose().

Missing Implementation:

  • Example: AnotherWidget uses a long-lived stream without any mechanism to unsubscribe when the widget is disposed of; it lacks a "dispose" stream and takeUntil.

Description:

  1. Identify Shared Streams: Determine which streams are being listened to by multiple components or multiple parts of your application. These are candidates for sharing.
  2. Choose the Appropriate Operator: RxDart provides these operators for sharing:
    • share(): Creates a shared broadcast stream. New listeners will only receive events that occur after they subscribe. This prevents the stream from being restarted for each new listener.
    • shareReplay(maxSize: n): Creates a shared broadcast stream that replays the last n events to new listeners. Useful for ensuring new listeners get the most recent data.
    • shareValue(seedValue: initialValue): Creates a shared stream that holds the latest value and provides it to new listeners. Similar to a BehaviorSubject, but the sharing is handled automatically.
  3. Apply the Operator: Call the chosen operator (from rxdart) on the original stream before any listeners are attached. Example: final sharedStream = myStream.shareReplay(maxSize: 1);.
  4. Use the Shared Stream: All listeners should now subscribe to the sharedStream, not the original myStream.

Threats Mitigated:

  • Unexpected Behavior due to Multiple Subscriptions: (Severity: Medium) - Prevents errors that can occur with single-subscription streams and ensures consistent behavior across multiple listeners.
  • Inconsistent State: (Severity: Medium) - Ensures that all parts of the application receive the same stream events, preventing inconsistencies in data or UI.
  • Resource Duplication: (Severity: Low) - Avoids unnecessary re-computation or re-fetching of data if multiple components are listening to the same underlying data source (e.g., a network request).

Impact:

  • Unexpected Behavior: Risk reduced significantly.
  • Inconsistent State: Risk reduced significantly.
  • Resource Duplication: Risk reduced.

Currently Implemented:

  • Example: A stream representing the currently logged-in user's data is shared using shareReplay(maxSize: 1) so that all parts of the UI can access the current user information without multiple fetches.

Missing Implementation:

  • Example: Multiple widgets are independently listening to the same raw network stream (without sharing), potentially causing multiple, redundant network requests and leading to inconsistent data if the requests return at different times.

Description:

  1. onError in listen(): While not strictly an RxDart operator, always providing an onError callback to the listen() method is crucial for initial error handling.
  2. catchError Operator: Use the catchError operator (from rxdart) within the stream pipeline to handle errors. This allows for more sophisticated error management:
    • Logging: Log the error details for debugging.
    • Default Values: Emit a default or fallback value to keep the stream alive.
    • Error Transformation: Transform the error into a different type of error that's more meaningful in the context.
    • Re-throwing: Re-throw the error (or a modified version) if it cannot be handled locally.
  3. retry and retryWhen Operators: For transient errors (e.g., temporary network issues), use the retry or retryWhen operators (from rxdart) to automatically attempt to re-subscribe to the stream. retryWhen gives you fine-grained control over the retry logic, allowing for things like exponential backoff.
  4. onErrorReturn, onErrorResumeNext: Use these operators to return a default value or switch to another stream in case of error.

Threats Mitigated:

  • Application Crashes: (Severity: Critical) - Prevents unhandled stream errors from propagating and crashing the application.
  • Inconsistent State: (Severity: Medium) - Allows for graceful recovery from errors, preventing the application from entering an invalid or inconsistent state.
  • Silent Failures: (Severity: Medium) - Ensures that errors are not silently ignored; they are logged, handled, and potentially reported.
  • Poor User Experience: (Severity: Low) - Provides a mechanism for displaying error messages to the user or taking corrective actions (e.g., retrying an operation).

Impact:

  • Application Crashes: Risk reduced significantly.
  • Inconsistent State: Risk reduced significantly.
  • Silent Failures: Risk reduced significantly.
  • Poor User Experience: Risk reduced.

Currently Implemented:

  • Example: All stream subscriptions in MyBloc have onError handlers. Additionally, network requests using streams utilize retryWhen with an exponential backoff strategy to handle temporary network interruptions. catchError is used to log errors and emit error states to the UI.

Missing Implementation:

  • Example: Some utility functions that use streams internally don't handle errors using catchError or retry, potentially leading to silent failures or crashes if the underlying data source fails.
  • Example: A stream subscription in a widget only handles the onData callback and completely ignores the possibility of errors.