0

It was too complicated description of the task, so let me edit it and simplify: I need a Drupal 7 function (data collection function) to be periodically triggered in the background but only a while a logged in.

Use case is that we need bunch of data from the remote server to display to the user and it takes long time for all the data to be (1) copied from the remote server, (2) upload to local Drupal database and (3) finally to display to the user. So we don't want the user to wait, but to do some other staff and display the data little bit later when it is ready.

So scenario is like this:

  • User logs in to Drupal 7 website and start browsing pages without any delay.
  • At the moment when he/she log's in we need to trigger a Drupal function in a custom module that starts to perform steps (1) and (2) in the background. So user again doesn't feel any delays.
  • When the data is pulled and uploaded to the local Drupal 7 database, the system starts displaying it to the user.

Is it so difficult or impossible for Drupal to trigger a function in the background independently of whatever user is doing on the website only for the period of time while he/she is in the session?

5
  • maybe you need a batch job? drupal.org/node/180528
    – hkoosha
    Commented Oct 18, 2014 at 6:41
  • batch job won't do in our case, but drupal.org/project/background_process where I found a link on drupal.org/node/180528 could do. researching this...
    – Sasha
    Commented Oct 20, 2014 at 19:54
  • Unfortunately, background_process also triggers the function at the same time as node loads thus slowing it down significantly. So my question is still not answered.
    – Sasha
    Commented Oct 20, 2014 at 20:13
  • I think you should provide more information and clarification. How is the background task handled? is it NodeJS? or some remote server? to check if user is still browsing you have to send heartbeats using javascript. In response to each heartbeat the server should answer if data is ready. But what should happen when it is ready? the user should be redirected? a popup should tell user about it? if it's a heavily loaded website I wouldn't suggest handling the heartbeats using Drupal itself. there was some module specific to Ajax request... can't remember the name.
    – hkoosha
    Commented Oct 21, 2014 at 12:20
  • I assumed you are not handling the heavy function in Drupal because of PHP's max execution time limit. if it is Drupal I can't think of a performance friendly way of stopping it. But if it's an external service and you set your cron to 5 minutes (and install elysia-cron too!) if the user has not sent a heartbeat for 5 minutes (checked during cron) you send an stop signal to external service.
    – hkoosha
    Commented Oct 21, 2014 at 12:24

2 Answers 2

3

Preface

While doing web development, if user is browsing can only be measured with two methods: 1. User last access time is less than A_PRE_DEFINED_AMOUNT (say 5 minutes) or (more accurately in some cases, as the user might be just reading the same page without refreshing) user's last heartbeat time is less than A_PRE_DEFINED_AMOUNT or or the most accurate, combination of both (js might be disabled on some browsers).

And while doing Drupal development, running a task periodically, means a cron task. but sometimes these tasks are better handled outside of Drupal.

The problem originates from the fact that Drupal does not have an always running server. Usually NodeJS is used in combination for such tasks, and it performs better (usually when resources on the server is limited). When the NodeJS has the data ready, it uploads it to drupal by calling a URL which you have previously defined in a custom module, say http://example.com/my/module/upload/data. It will use HTTP POST to authenticate and send data.

But anyway, here is the pure Drupal solution:

The custom module part:

  • Create a custom module.
  • Define a new entity type in your module (recommended) using a project like entityspice, model or plain entity api.
  • OR just use hook_schema() and define a table for your module in database (though entity is recommended).
  • Define a custom rule action in your module, when called it should write the current user id (uid) in the table you just defined. If you have created a new entity type you can use the already existing rule create a new entity.
  • Or just use nodes or a field in user profile but be careful about access checks. It's your call.
  • Create another rule action, triggered on cron (using rules_scheduler), in this action: fetch user's last access time, if it's more than 5 minutes, fetch the entity we talked about just above and remove it (more explanation on this later).
  • OR instead of rule action, define a cron task in your module. it should: check user's last access time, if it is more than 5 minutes, remove his/her uid from your custom table.
  • Or instead of checking user last access time, create a custom JS loaded on every page for logged in users which sends a heartbeat. It is simply done by defining a custom path in your module such as example.com/my/heartbeat/path and fetching that URL using HTTP GET (using ajax) on the server in your custom path's handler function, you should check current user's UID who is sending the heartbeat and update the last access time of the user. (or perhaps create your own entity/table/field for storing last heartbeat time).

Congrats! first part done. now you know if you should process data or stop processing data for user. You know you should while their UID is in your custom table.

The processing of data:

Still this part of question is unclear: who will fetch data? is there a large amount of data, or does it take long for data to become ready? if the former, you might face PHP execution time outs, if the later, you might be fine. Any way, to do it in Drupal, you should create a custom cron task which is again, possible in rules. in this cron task, check every uid which is in the custom entity/table we talked about above and fetch data for, and upload it to local database.

Your cron task should check two conditions: 1. is user uid still in table? 2. is data ready? if 1 is false, cancel fetching data. If 2 is false, just pass for now.

If there is a large amount of data being fetched and user has left the site, you probably won't be able to cancel it as there is no true multi-threading in PHP. how ever there is a register_tick_function which might do the job: if the uid of current task is removed, call die(). I don't know if die() in registered tick function works, however if data is being fetched for other users too, they will be canceled!!

final note:

DO Install Elysia Cron (optional but highly recommended). If you are planning to run cron on short periods it will take the load off of your server drastically.

2
  • Thank you. This answer is the most detailed and most relevant one, so I have to mark it as accepted even though it's not implemented yet. @loolooyyyy, are you available for hire? Your website koosha.cc, btw, doesn't work.
    – Sasha
    Commented Oct 27, 2014 at 23:18
  • @Sasha It's an honor to work with you but unfortunately I do not have free time. and yes there is no webserver running on koosha.cc :)
    – hkoosha
    Commented Oct 28, 2014 at 7:06
0

Cron won't help you, it executes the jobs in system(machine) level, at specified time, it doesn't care about someone is viewing the website, how many minutes he/she sits.. You should look for client side solutions, which interact with browser (JavaScript, or it's framework JQuery...), may be setTimeout or setInterval can help you..

Some thoughts:

function MYMODULE_menu()
{
  $items['PATH'] = [
    'title' => 'TITLE',
    'page callback' => 'myCallback',
    'page arguments' => [],
    'access callback' => TRUE, // Bad idea,
    'type' => MENU_CALLBACK
  ];

  return $items;
}

function myCallback()
{
  // buildTheLogic();
}

javascript:

function someName()
{
  // buildTheLogicHere();
  // may be you call ajax request to that PATH, display the result...
}

setTimeout(someName, 60000);

you can also use self-executing anonymous functions with setTimeout if you want to repeat the process.

3
  • Tried this approach, unfortunately it won't run in the background.
    – Sasha
    Commented Oct 20, 2014 at 19:55
  • @Sasha are you sure it's not working? may be error is in your code?
    – xurshid29
    Commented Oct 21, 2014 at 3:09
  • @Sasha your task is simple, either my code (snippet)... You should post what you did that we may find what's going wrong..
    – xurshid29
    Commented Oct 21, 2014 at 3:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.