def _register_wait_page_visit(self):
     if otree.common_internal.USE_REDIS:
         lock = redis_lock.Lock(otree.common_internal.get_redis_conn(),
                                self.get_channels_group_name(),
                                expire=60,
                                auto_renewal=True)
     else:
         lock = global_lock()
     with lock:
         unvisited_participants = self._tally_unvisited()
     if unvisited_participants:
         return
     try:
         if self.wait_for_all_groups:
             completion = CompletedSubsessionWaitPage(
                 page_index=self._index_in_pages, session=self.session)
         else:
             completion = CompletedGroupWaitPage(
                 page_index=self._index_in_pages,
                 group_pk=self.group.pk,
                 session=self.session)
         completion.save()
         return completion
     # if the record already exists
     # (enforced through unique_together)
     except django.db.IntegrityError:
         return
Exemple #2
0
    def _mark_completed_and_notify(self, group: Optional[BaseGroup]):
        # if group is not passed, then it's the whole subsession
        # could be 2 people creating the record at the same time
        # in _increment_index_in_pages, so could end up creating 2 records
        # but it's not a problem.

        base_kwargs = dict(page_index=self._index_in_pages, session_id=self._session_pk)
        Player = self.PlayerClass

        if self.wait_for_all_groups:
            CompletedSubsessionWaitPage.objects_create(**base_kwargs)
        elif self.group_by_arrival_time:
            db.add(
                CompletedGBATWaitPage(
                    **base_kwargs, id_in_subsession=group.id_in_subsession
                )
            )
        else:
            db.add(CompletedGroupWaitPage(**base_kwargs, group_id=group.id))

        participants = self._get_participants_for_this_waitpage()
        self._mark_page_completions(list(participants))
        for pp in participants:
            pp._last_page_timestamp = time.time()

        # this can cause messages to get wrongly enqueued in the botworker
        if otree.common.USE_TIMEOUT_WORKER and not self.participant.is_browser_bot:
            # 2016-11-15: we used to only ensure the next page is visited
            # if the next page has a timeout, or if it's a wait page
            # but this is not reliable because next page might be skipped anyway,
            # and we don't know what page will actually be shown next to the user.
            otree.tasks.ensure_pages_visited(
                participant_pks=[pp.id for pp in participants], delay=10,
            )

        if self.group_by_arrival_time:
            channel_utils.sync_group_send(
                group=channel_utils.gbat_group_name(**base_kwargs),
                data={'status': 'ready'},
            )
        else:
            if self.wait_for_all_groups:
                channels_group_name = channel_utils.subsession_wait_page_name(
                    **base_kwargs
                )
            else:
                channels_group_name = channel_utils.group_wait_page_name(
                    **base_kwargs, group_id=group.id
                )

            channel_utils.sync_group_send(
                group=channels_group_name, data={'status': 'ready'}
            )
Exemple #3
0
 def inner_dispatch_group(self):
     ## EARLY EXITS
     if CompletedGroupWaitPage.objects_exists(
         page_index=self._index_in_pages,
         group_id=self.player.group_id,
         session_id=self._session_pk,
     ):
         return self._response_when_ready()
     is_displayed = self._is_displayed()
     is_last, someone_waiting = self._tally_unvisited()
     if is_displayed and not is_last:
         return self._get_wait_page()
     elif is_last and (someone_waiting or is_displayed):
         self._run_aapa_and_notify(self.group)
     return self._response_when_ready()
Exemple #4
0
 def _register_wait_page_visit(self):
     with global_lock():
         unvisited_participants = self._tally_unvisited()
     if unvisited_participants:
         return
     try:
         if self.wait_for_all_groups:
             completion = CompletedSubsessionWaitPage(
                 page_index=self._index_in_pages, session=self.session)
         else:
             completion = CompletedGroupWaitPage(
                 page_index=self._index_in_pages,
                 group_pk=self.group.pk,
                 session=self.session)
         completion.save()
         return completion
     # if the record already exists
     # (enforced through unique_together)
     except django.db.IntegrityError:
         return
Exemple #5
0
    def dispatch(self, request, *args, **kwargs):
        if self.wait_for_all_groups:
            self._group_or_subsession = self.subsession
        else:
            self._group_or_subsession = self.group
        if self._is_ready():
            return self._response_when_ready()
        # take a lock because we set "waiting for" list here
        with lock_on_this_code_path():
            unvisited_participants = self._tally_unvisited()
        if unvisited_participants:
            # only skip the wait page if there are still
            # unvisited participants. otherwise, you need to
            # mark the page as completed.
            # see _increment_index_in_pages, which needs to
            # handle this case also
            if not self.is_displayed():
                return self._response_when_ready()
            self.participant.is_on_wait_page = True
            return self._get_wait_page()
        else:
            try:
                if self.wait_for_all_groups:
                    completion = CompletedSubsessionWaitPage(
                        page_index=self._index_in_pages,
                        session_pk=self.session.pk)
                else:
                    completion = CompletedGroupWaitPage(
                        page_index=self._index_in_pages,
                        group_pk=self.group.pk,
                        session_pk=self.session.pk)
                completion.save()
            # if the record already exists
            # (enforced through unique_together)
            except django.db.IntegrityError:
                self.participant.is_on_wait_page = True
                return self._get_wait_page()
            try:
                # need to check this before deleting
                # reference to self.player
                is_displayed = self.is_displayed()

                # in case there is a timeout on the next page, we
                # should ensure the next pages are visited promptly
                # TODO: can we make this run only if next page is a
                # timeout page?
                # or if a player is auto playing.
                # we could instead make this request the current page
                # URL, but it's different for each player

                # _group_or_subsession might be deleted
                # in after_all_players_arrive, so calculate this first
                participant_pk_set = set([
                    p.participant.pk
                    for p in self._group_or_subsession.player_set.all()
                ])

                # _group_or_subsession might be deleted
                # in after_all_players_arrive, so calculate this first
                channels_group_name = self.channels_group_name()

                # block users from accessing self.player inside
                # after_all_players_arrive, because conceptually
                # there is no single player in this context
                # (method is executed once for the whole group)

                player = self.player
                del self.player

                # make sure we get the most up-to-date player objects
                # e.g. if they were queried in is_displayed(),
                # then they could be out of date
                # but don't delete the current player from cache
                # because we need it to be saved at the end
                import idmap.tls
                cache = getattr(idmap.tls._tls, 'idmap_cache', {})
                for p in list(cache.get(self.PlayerClass, {}).values()):
                    if p != player:
                        self.PlayerClass.flush_cached_instance(p)

                # if any player can skip the wait page,
                # then we shouldn't run after_all_players_arrive
                # because if some players are able to proceed to the next page
                # before after_all_players_arrive is run,
                # then after_all_players_arrive is probably not essential.
                # often, there are some wait pages that all players skip,
                # because they should only be shown in certain rounds.
                # maybe the fields that after_all_players_arrive depends on
                # are null
                # something to think about: ideally, should we check if
                # all players skipped, or any player skipped?
                # as a shortcut, we just check if is_displayed is true
                # for the last player.
                if is_displayed:
                    self.after_all_players_arrive()
            except Exception as e:
                completion.delete()
                raise e

            self.player = player

            if otree.common_internal.USE_REDIS:
                # 2015-07-27:
                #   why not check if the next page has_timeout?
                otree.timeout.tasks.ensure_pages_visited.schedule(kwargs={
                    'participant_pk_set':
                    participant_pk_set,
                    'wait_page_index':
                    self._index_in_pages,
                },
                                                                  delay=10)

            completion.after_all_players_arrive_run = True
            completion.save()

            # send a message to the channel to move forward
            # this should happen at the very end,
            channels.Group(channels_group_name).send(
                {'text': json.dumps({'status': 'ready'})})

            # we can assume it's ready because
            # even if it wasn't created, that means someone else
            # created it, and therefore that whole code block
            # finished executing (including the after_all_players_arrive)
            # inside the transaction
            return self._response_when_ready()
Exemple #6
0
 def completion_exists(self, **kwargs):
     return CompletedGroupWaitPage.objects_exists(**kwargs)