Showing posts with label background-process. Show all posts
Showing posts with label background-process. Show all posts

Sunday, September 18, 2016

limited number of user-initiated background processes

Leave a Comment

I need to allow users to submit requests for very, very large jobs. We are talking 100 gigabytes of memory and 20 hours of computing time. This costs our company a lot of money, so it was stipulated that only 2 jobs could be running at any time, and requests for new jobs when 2 are already running would be rejected (and the user notified that the server is busy).

My current solution uses an Executor from concurrent.futures, and requires setting the Apache server to run only one process, reducing responsiveness (current user count is very low, so it's okay for now).

If possible I would like to use Celery for this, but I did not see in the documentation any way to accomplish this particular setting.

How can I run up to a limited number of jobs in the background in a Django application, and notify users when jobs are rejected because the server is busy?

3 Answers

Answers 1

I have two solutions for this particular case, one an out of the box solution by celery, and another one that you implement yourself.

  1. You can do something like this with celery workers. In particular, you only create two worker processes with concurrency=1 (or well, one with concurrency=2, but that's gonna be threads, not different processes), this way, only two jobs can be done asynchronously. Now you need a way to raise exceptions if both jobs are occupied, then you use inspect, to count the number of active tasks and throw exceptions if required. For implementation, you can checkout this SO post.

You might also be interested in rate limits.

  1. You can do it all yourself, using a locking solution of choice. In particular, a nice implementation that makes sure only two processes are running with redis (and redis-py) is as simple as the following. (Considering you know redis, since you know celery)

    from redis import StrictRedis  redis = StrictRedis('localhost', '6379') locks = ['compute:lock1', 'compute:lock2'] for key in locks:     lock = redis.lock(key, blocking_timeout=5)     acquired = lock.acquire()     if acquired:         do_huge_computation()         lock.release()     else:         raise SystemLimitsReached("Already at max capacity !") 

This way you make sure only two running processes can exist in the system. A third processes will block in the line lock = redis.lock(key) for blocking_timeout seconds, if the locking was successful, acquired would be True, else it's False and you'd tell your user to wait !

I had the same requirement sometime in the past and what I ended up coding was something like the solution above. In particular

  1. This has the least amount of race conditions possible
  2. It's easy to read
  3. Doesn't depend on a sysadmin, suddenly doubling the concurrency of workers under load and blowing up the whole system.
  4. You can also implement the limit per user, meaning each user can have 2 simultaneous running jobs, by only changing the lock keys from compute:lock1 to compute:userId:lock1 and lock2 accordingly. You can't do this one with vanila celery.

Answers 2

First of all you need to limit concurrency on your worker (docs):

celery -A proj worker --loglevel=INFO --concurrency=2 -n <worker_name> 

This will help to make sure that you do not have more than 2 active tasks even if you will have errors in the code.

Now you have 2 ways to implement task number validation:

  1. You can use inspect to get number of active and scheduled tasks:

     from celery import current_app   def start_job():       inspect = current_app.control.inspect()       active_tasks = inspect.active() or {}       scheduled_tasks = inspect.scheduled() or {}       worker_key = 'celery@%s' % <worker_name>       worker_tasks = active_tasks.get(worker_key, []) + scheduled_tasks.get(worker_key, [])       if len(worker_tasks) >= 2:           raise MyCustomException('It is impossible to start more than 2 tasks.')        else:           my_task.delay() 
  2. You can store number of currently executing tasks in DB and validate task execution based on it.

Second approach could be better if you want to scale your functionality - introduce premium users or do not allow to execute 2 requests by one user.

Answers 3

First

You need the first part of SpiXel's solution. According to him, "you only create two worker processes with concurrency=1".

Second

Set the time out for the task waiting in the queue, which is set CELERY_EVENT_QUEUE_TTL and the queue length limit according to how to limit number of tasks in queue and stop feeding when full?.

Therefore, when the two work running jobs, and the task in the queue waiting like 10 sec or any period time you like, the task will be time out. Or if the queue has been fulfilled, new arrival tasks will be dropped out.

Third

you need extra things to deal with notifying "users when jobs are rejected because the server is busy".

Dead Letter Exchanges is what you need. Every time a task is failed because of the queue length limit or message timeout. "Messages will be dropped or dead-lettered from the front of the queue to make room for new messages once the limit is reached."

You can set "x-dead-letter-exchange" to route to another queue, once this queue receive the dead lettered message, you can send a notification message to users.

Read More

Friday, March 25, 2016

EventBus : Activity does not receive event when app is in the background

Leave a Comment

I'm using EventBus to communicate between Activity and Service. Today I got a problem and don't know why.

  1. I have Activity, Fragment and Service. All of them are working fine.

  2. In Activity and Fragment I registered them to Receive events which delivered from Service

  3. In Activity and Fragment, I un-register them when onDestroy() was called.

  4. In normal cases, when Services delivers events, Fragment and Activity can receive those events and work well.

  5. But when App is pushed on the background (by presses Home or Power button), only Fragment receives events which delivered from Service, and Activity does not receive them.

  6. I did not do anything in onPause() both of Activity and Fragment.

Question:

Is there any explanation for that? And how can I make my Activity receives event like Fragment did when app is pushed on background?

4 Answers

Answers 1

When user presses back/home button, Activity can be destroyed anytime and thus you won't be able to receive the data using EventBus. If any how you are trying to receive the data when the Activity is in background, it may leak memory and the app will crash.

There can be other approaches to get the data in the Activity when user resumes the activity.

You can either user sharedpreferences or local database to save the results passed be the service. And when the user navigates back to the activity, read it from sharedpreferences or database.

This way there won't be any issue with memory leakage or data loss.

Edit 1:

It is always recommended to unregister listeners in either onPause or onStop because the activity does not need those events when it is not in the foreground. And since onDestroy() is not guaranteed to be called, thus you could continue receiving broadcasts when the Activity is no longer open.

Answers 2

The Activity class provides two lifecycle methods, onStop() and onRestart() when is not visible (background mode), which allow you to specifically handle how your activity handles being stopped and restarted. Unlike the paused state, which identifies a partial UI obstruction, the stopped state guarantees that the UI is no longer visible and the user's focus is in a separate activity (or an entirely separate app).

In order to understand this cycle, take a look a this image that shows the flow when your app goes out of foreground mode.

When the user leaves your activity

In your case you can handle the issue like this.

  • Provide to the user a mechanism to save persistent application data using either local database(Sqlite), sharedPreferences.
  • Handle your data persistency on onStop() method.
  • When user callback your app, then you will need to restore the data using onRestart() method.

    Here is the way to implement that.

    public class Calc extends Activity { public static final String PREFS_NAME = "MyPrefsFile";  @Override protected void onCreate(Bundle state){    super.onCreate(state);    . . .     // Restore preferences    SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);    boolean silent = settings.getBoolean("silentMode", false);    setSilent(silent); }  @Override protected void onStop(){    super.onStop();    // We need an Editor object to make preference changes.   // All objects are from android.context.Context   SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);   SharedPreferences.Editor editor = settings.edit();   editor.putBoolean("silentMode", mSilentMode);    // Commit the edits!   editor.commit(); } 

    }

Please read the following documentation from Android Developer site

Answers 3

It's hard to guess what you have done which causing this behavior, consider providing some code.

But what is obvious is that you have some design flaws.

You must unregister from any event bus or listener in your ui components like Activities or Fragments when user navigates back from the app, if you don't, there is a good chance to leak your activity and all of the resources which it holds.

You should store any data which you receive or calculate in your background service to a file or database, when user open or reopen your app you should check for that data and act on it.

Answers 4

Needs more codes or examples to properly help you. But try the following.

  1. Are your activities extended from a baseActivity? and if so remove the onDestroy eventbus unregister code and check.
  2. In developer options, check whether the don't keep activities option is unchecked.
  3. Pressing back will kill your app anyway unless u have overridden the back event.
Read More