Problem/Motivation
Since the changes for #3164669: Private message threads collapsed when they shouldn't be it seems that the mapper may return a thread does not contain *all* users requested. I was testing the latest dev version and noticed how my test user would end up adding a message sent to another user to a thread that only contained the test user itself (this obviously is a glitch, although I can imagine it happening when a user is deleted).
Steps to reproduce
- Start with a clean install (just to be sure there are no threads in the system yet)
- Create three users; alice, bob and charlie. Make sure they have the appropriate permissions, i.e. use private message system and view user information
- Log in as alice
- Go and create a private message to bob
- See how everything works as expected
- Go and create another private message, but select both bob and charlie as recipients
- Notice how your new message ends up as a new message in the thread you shared with bob, and charlie is nowhere to be found
Expected: a new thread was created for the combination alice, bob and charlie.
Proposed resolution
TBD. I would hope it is possible to formulate a SQL query to select a thread with all the passed users but *only* the passed users. Although I think that started the whole problem in the first place, so maybe we should just add more conditions on the checks we use when selecting a thread (we're only checking if the there users are in the set of relevant users, but not whether they are *all* in the thread, AFAICT).
Remaining tasks
- Agree on approach/create merge request
- Extend user test
- Review
User interface changes
None.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#9 | private_message-rturned_thread_may_exclude_users-3217509-9.patch | 1.48 KB | artem_sylchuk |
Issue fork private_message-3217509
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
artem_sylchukNo, the PrivateMessageMapper::getThreadIdForMembers first selects all the threads where all given users are presents and then iterates over them to find the first thread where there is no more users except the given ones. I quickly checked the code and didn't find obvious reasons for the described issue.
But in fact the forcing thread to be unique for the given set of users brings so many problems so I'm not sure what the purpose of it in general.
https://www.drupal.org/project/private_message/issues/2942602
https://www.drupal.org/project/private_message/issues/3158310#comment-14...
Plus for example, as you mentioned the user deleted from the site may cause multiple thread duplications.
So I'd like to remove that restriction and not force thread uniqueness.
Any suggestions, thoughts, concerns will be appreciated.
Comment #3
eelkeblokTBH, I don't fully understand that either. I suppose it is to get to a model similar to Whatsapp and such apps where you have a unique conversation per other user or group of users. I am more familiar with PM models where it is more like email, where you create a message with a subject and might have multiple such threads, with the same or different users. Do note that I think a subject will be mandatory, in that case.
[I did understand the logic, but what I think is happening is that the logic fails to check if *all* users are part of the found conversation.]
Comment #4
eelkeblokWRT to the actual issue, I'll probably be doing a deeper dive this week to try and understand it better. Will try to get some better steps to reproduce too.
Comment #5
eelkeblokI did a deeper dive. Although the code indeed claims to find all threads where all given users are present, in fact it returns all threads any of the users are involved in*. It then proceeds to filter out threads that have other users too, but does so under the assumption that the threads the earlier query turned up at least has all of the users we are looking for. This is a wrong assumption. To make it easier to follow, this is the relevant code:
I think the mistake is basically in the line
This effectively serves as an OR, not an AND, for all the user IDs in the $uids array. In many cases, this problem is then fixed by actually filtering out the threads with other users, but it may still turn up threads that do not have all users we are looking for.
I will push a fix shortly, that solves my specific test case. I'll also try and come up with a test that actually demonstrates the problem.
*: In my case, this is nicely demonstrated in the debugger, where the query comes up with dozens of threads; one of the users is a community manager who has threads with a lot of other users; just one with the second user involved in the lookup, though. Because there happens to be also a "corrupt" thread where she is the only member, and that thread happens to be hit on first in the filtering loop, that is what the method produces instead of the actual common thread between the two users involved.
Comment #6
eelkeblokAdding steps to reproduce. Note that this bug only exists in the current dev version, not in beta16.
Comment #7
eelkeblokBranch tests-only contains a test that basically replicates the steps to reproduce. Unfortunately, the existing thread members test surfaced that my first stab at the solution doesn't cut it either.
Comment #8
eelkeblokBasically, I now reversed the problem (the query is now basically doing what was inteded before, so it still needs the filtering to not find threads that have extra members). I'll restore the "filter" code.
Comment #9
artem_sylchukHi @eelkeblok, thanks for the instructions, now I see why it is happening.
Actually #3154558: 1116 Too many tables; MariaDB can only use 61 tables in a join was the issue where the change was introduced and it had reasons to replace the working solution with joins to something else.
However it was relying on the count of the members in the thread to identify the correct one and it was wrong too (see #3164669: Private message threads collapsed when they shouldn't be).
Looks like there is no good way to do that in SQL so probably we can do that in PHP.
Attaching my quick attempt as a patch. Not sure about the performance of this solution, it may cause problems if there a lot of threads with many common users.
Comment #11
eelkeblokSee my branch, I think with the filtering code restored it will be a somewhat efficient solution. I'll create an MR to make it more visible.
Comment #13
eelkeblokYour patch looks OK too. I guess the test I wrote in https://git.drupalcode.org/project/private_message/-/merge_requests/9 should also work for it (you could create a branch for your patch and merge in the tests-only branch, that should let the test verify whether your solution is valid).
I think the groupBy might still be relevant, as depending on the number of users the query would now return duplicates for common threads. Question is what has more overhead. My solution (in MR 8) is more SQL-centric, but the basis question remains the same; more basic SQL vs. more processing in PHP.
Comment #14
eelkeblokSetting to Needs review. Quick summary:
Comment #15
eelkeblokComment #17
dinazaur CreditAttribution: dinazaur as a volunteer and at DevBranch commentedHello,
I've tried to reproduce error from the 3154558 issue, since approach provided by @eelkeblok uses SQL. I've written the test that generates "treads' with 63+ users few times in a row with different combinations of users, and the test passed. Don't want to commit it because it will only overload Drupal CI without any reasonable testability.
Comment #19
artem_sylchukThanks to everyone, I think it is the main blocker for releasing a new beta, so I'm happy to merge it.
Comment #20
artem_sylchukDuring the code sprint @dinazaur had a brilliant idea to create the members hash field in the thread entity. It will completely eliminate the problem.
However, it will require to update all existing threads.
I think it worth to keep it noted.