Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi Triggers -> Develop #395

Merged
merged 52 commits into from
Jan 22, 2025
Merged

Multi Triggers -> Develop #395

merged 52 commits into from
Jan 22, 2025

Conversation

akashvercetti
Copy link
Collaborator

No description provided.

akashvercetti and others added 30 commits October 30, 2024 10:52
- TODO: Call IsEventFirstTime from the new db/localDataStore class
- Renamed one letter object names to fit the context
- TODO: Call appropriate persist method from the new db/localDataStore class
- Added new class CTEventDatabase to store user events in database.
- Added create table, insert, update and check event methods.
…ofile-events

[MC-2088] Evaluate first time profile events
…ents

 [MC-2086] Evaluate first time events
- synchronised userEventLogs set access
…e to the database. updated tests. added normalised names to queries.
Copy link
Contributor

@nzagorchev nzagorchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In - (void)processEvent:(NSDictionary *)event withType:(CleverTapEventType)eventType the event is written to the local storage and further down in the same method the evaluation manager is called to evaluate the in-apps for the event.
The local data store calls persistEvent which calls self.dbHelper upsertEvent in a background queue with dispatch_async. The database then runs the query in the database queue.
The evaluation is done on the serial queue on which the events are processed and queued.
The current flow is persistEvent, upsertEvent, evaluateOnEvent, eventExists, update success, upsert success. However, this does not seem guaranteed since code runs on different queues. This opens for potential race conditions. Please check this.

Please check the comments left. I have not finished reviewing all files but I believe the feedback provided is enough for discussion. I will leave additional comments if I have more suggestions.

dispatch_queue_t _databaseQueue;
}

+ (instancetype)sharedInstanceWithConfig:(CleverTapInstanceConfig *)config {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CTEventDatabase shared instance with config is bit misleading that it can return a different instance based on the config and use a shared instance with same configs. Do we want separate databases per account id?
The CTEventDatabase is only used in the local store, does it need to be a singleton?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed config part and still used singleton as local store init can be called from other CT instance also. We are not using separate database per account id here. Any suggestion if we want to use separate database per account id?

CleverTapSDK/EventDatabase/CTEventDatabase.m Outdated Show resolved Hide resolved
CleverTapSDK/EventDatabase/CTEventDatabase.m Outdated Show resolved Hide resolved
CleverTapSDK/EventDatabase/CTEventDatabase.m Outdated Show resolved Hide resolved
CleverTapSDK/EventDatabase/CTEventDatabase.m Outdated Show resolved Hide resolved
CleverTapSDKTests/CTLocalDataStoreTests.m Outdated Show resolved Hide resolved
CleverTapSDKTests/CTLocalDataStoreTests.m Outdated Show resolved Hide resolved
CleverTapSDKTests/CTLocalDataStoreTests.m Outdated Show resolved Hide resolved
CleverTapSDK/EventDatabase/CTEventDatabase.m Show resolved Hide resolved
@nishant-clevertap
Copy link
Contributor

In - (void)processEvent:(NSDictionary *)event withType:(CleverTapEventType)eventType the event is written to the local storage and further down in the same method the evaluation manager is called to evaluate the in-apps for the event. The local data store calls persistEvent which calls self.dbHelper upsertEvent in a background queue with dispatch_async. The database then runs the query in the database queue. The evaluation is done on the serial queue on which the events are processed and queued. The current flow is persistEvent, upsertEvent, evaluateOnEvent, eventExists, update success, upsert success. However, this does not seem guaranteed since code runs on different queues. This opens for potential race conditions. Please check this.

Please check the comments left. I have not finished reviewing all files but I believe the feedback provided is enough for discussion. I will leave additional comments if I have more suggestions.

@nzagorchev I have updated code to avoid race condition, used same dispatch manager serial queue in db operations also. Kindly review new changes.

- Added proper return for error and records not found.
- Removed get first and last time public API as per TAN.
- Minor code improvements.
CleverTapSDK/CTLocalDataStore.h Outdated Show resolved Hide resolved
CleverTapSDK/CTLocalDataStore.h Outdated Show resolved Hide resolved
CleverTapSDK/CTLocalDataStore.m Outdated Show resolved Hide resolved
CleverTapSDK/CTLocalDataStore.h Outdated Show resolved Hide resolved
}
}
NSInteger count = [self.dbHelper getEventCount:normalizedName deviceID:self.deviceInfo.deviceId];
if (count > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this check be count > 0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is updated as per TAN, as the event is stored in the LocalDataStore before the evaluation. This means a record for the event will always exist in the data store. If the event count is 1, it is a first time event. Refer: https://wizrocket.atlassian.net/wiki/spaces/EN/pages/4464115774/TAN+SDK+In-app+Multi-triggers#LocalDataStore

Also the same changes are in Android PR: https://github.com/CleverTap/clevertap-android-sdk/pull/683/files#diff-3e307aa90ae0394b024172f2b91b3c392c33c49380e4895becadaab6633f4e86R283

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this is for the boolean check count == 1 but if it is recorded in the database, we can add it to the set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes got it, Should I update it like:

NSInteger count = [self.dbHelper getEventCount:normalizedName deviceID:self.deviceInfo.deviceId];
if (count > 0) {
    @synchronized (self.userEventLogs) {
        [self.userEventLogs addObject:normalizedName];
    }
}
return count == 1;
    ```

CleverTapSDK/EventDatabase/CTEventDatabase.m Outdated Show resolved Hide resolved
CleverTapSDK/EventDatabase/CTEventDatabase.m Outdated Show resolved Hide resolved
CleverTapSDK/EventDatabase/CTEventDatabase.m Outdated Show resolved Hide resolved
CleverTapSDK/EventDatabase/CTEventDatabase.m Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are potential deadlocks where the dispatch_semaphore_wait is used in this class' methods.
The implementation can cause a deadlock if called from the same serial queue managed by the DispatchQueueManager. This happens because:

  • The method uses a semaphore to wait for a task to complete.
  • If the method is called while already running on the serial queue, the dispatch_async in runSerialAsync: will execute the task synchronously. The semaphore wait will block the queue, preventing the task from completing and thus causing a deadlock.

There are two options to solve this:

  1. Rewrite the code to add checks where the method is run from:
void (^taskBlock)(void) = ^{
    sqlite3_stmt *statement;
...
    sqlite3_finalize(statement);
 };

if ([self.dispatchQueueManager inSerialQueue]) {
    // If already on the serial queue, execute directly without semaphore
    taskBlock();
} else {
     // Otherwise, use semaphore for synchronous execution
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
     [self.dispatchQueueManager runSerialAsync:^{
          taskBlock();
          dispatch_semaphore_signal(semaphore);
      }];
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

return result;
  1. Rewrite the code to use completion blocks.
[self.dispatchQueueManager runSerialAsync:^{
        ...
        if (completion) {
            // Dispatch callback on main queue
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(result);
            });
        }
    }];

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated code to use option 1 by adding semaphore only when it is not in serial queue, kindly review the changes for this.

Copy link

coderabbitai bot commented Jan 21, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nishant-clevertap nishant-clevertap marked this pull request as ready for review January 21, 2025 09:41
@nishant-clevertap nishant-clevertap merged commit a1e8ade into develop Jan 22, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants