Пример #1
0
    def _createHistogramJob(self, **kwargs):
        from girder.plugins.jobs.models.job import Job
        from girder.plugins.jobs.constants import JobStatus
        from girder.plugins.histogram.models.histogram import Histogram

        file, item = self._uploadFile('plugins/large_image/plugin_tests/test_files/test_L_8.png')

        token = Token().createToken(self.admin)

        doc = Histogram().createHistogramJob(item, file, user=self.admin,
                                             token=token, **kwargs)

        complete = (JobStatus.SUCCESS, JobStatus.ERROR, JobStatus.CANCELED)
        starttime = time.time()
        while True:
            self.assertTrue(time.time() - starttime < 30)
            job = Job().load(doc['_id'], user=self.admin, exc=True)
            if job.get('status') in complete:
                break
            time.sleep(0.1)
        if job.get('log'):
            print(job.get('log'))
        assert job.get('status') == JobStatus.SUCCESS

        return ObjectId(job['_id'])
Пример #2
0
 def deleteIncompleteTiles(self, params):
     result = {'removed': 0}
     while True:
         item = Item().findOne({'largeImage.expected': True})
         if not item:
             break
         job = Job().load(item['largeImage']['jobId'], force=True)
         if job and job.get('status') in (
                 JobStatus.QUEUED, JobStatus.RUNNING):
             job = Job().cancelJob(job)
         if job and job.get('status') in (
                 JobStatus.QUEUED, JobStatus.RUNNING):
             result['message'] = ('The job for item %s could not be '
                                  'canceled' % (str(item['_id'])))
             break
         ImageItem().delete(item)
         result['removed'] += 1
     return result
Пример #3
0
    def delete(self, item, skipFileIds=None):
        deleted = False
        if 'largeImage' in item:
            job = None
            if 'jobId' in item['largeImage']:
                try:
                    job = Job().load(item['largeImage']['jobId'],
                                     force=True,
                                     exc=True)
                except ValidationException:
                    # The job has been deleted, but we still need to clean up
                    # the rest of the tile information
                    pass
            if (item['largeImage'].get('expected') and job
                    and job.get('status')
                    in (JobStatus.QUEUED, JobStatus.RUNNING)):
                # cannot cleanly remove the large image, since a conversion
                # job is currently in progress
                # TODO: cancel the job
                # TODO: return a failure error code
                return False

            # If this file was created by the worker job, delete it
            if 'jobId' in item['largeImage']:
                if job:
                    # TODO: does this eliminate all traces of the job?
                    # TODO: do we want to remove the original job?
                    Job().remove(job)
                del item['largeImage']['jobId']

            if 'originalId' in item['largeImage']:
                # The large image file should not be the original file
                assert item['largeImage']['originalId'] != \
                    item['largeImage'].get('fileId')

                if ('fileId' in item['largeImage'] and
                    (not skipFileIds
                     or item['largeImage']['fileId'] not in skipFileIds)):
                    file = File().load(id=item['largeImage']['fileId'],
                                       force=True)
                    if file:
                        File().remove(file)
                del item['largeImage']['originalId']

            del item['largeImage']

            item = self.save(item)
            deleted = True
        self.removeThumbnailFiles(item)
        return deleted
Пример #4
0
def createThumbnailsJob(job):
    """
    Create thumbnails for all of the large image items.

    :param spec: an array, each entry of which is the parameter dictionary for
        the model getThumbnail function.
    """
    job = Job().updateJob(
        job, log='Started creating large image thumbnails\n',
        status=JobStatus.RUNNING)
    checkedOrCreated = 0
    failedImages = 0
    try:
        spec = job['kwargs']['spec']
        logInterval = float(job['kwargs'].get('logInterval', 10))
        for entry in spec:
            job = Job().updateJob(
                job, log='Creating thumbnails for %r\n' % entry)
            lastLogTime = time.time()
            items = Item().find({'largeImage.fileId': {'$exists': True}})
            for item in items:
                try:
                    ImageItem().getThumbnail(item, **entry)
                    checkedOrCreated += 1
                except TileGeneralException as exc:
                    failedImages += 1
                    lastFailed = str(item['_id'])
                    logger.info('Failed to get thumbnail for item %s: %r' % (
                        lastFailed, exc))
                # Periodically, log the state of the job and check if it was
                # deleted or canceled.
                if time.time() - lastLogTime > logInterval:
                    job = Job().updateJob(
                        job, log='Checked or created %d thumbnail file%s\n' % (
                            checkedOrCreated,
                            's' if checkedOrCreated != 1 else ''))
                    if failedImages:
                        job = Job().updateJob(
                            job, log='Failed on %d thumbnail file%s (last '
                            'failure on item %s)\n' % (
                                failedImages,
                                's' if failedImages != 1 else '', lastFailed))
                    lastLogTime = time.time()
                    # Check if the job was deleted or canceled; if so, quit
                    job = Job().load(id=job['_id'], force=True)
                    if (not job or job['status'] in (
                            JobStatus.CANCELED, JobStatus.ERROR)):
                        cause = {
                            None: 'deleted',
                            JobStatus.CANCELED: 'canceled',
                            JobStatus.ERROR: 'stopped due to error',
                        }[None if not job else job.get('status')]
                        logger.info('Large image thumbnails job %s' % cause)
                        return
    except Exception:
        logger.exception('Error with large image create thumbnails job')
        job = Job().updateJob(
            job, log='Error creating large image thumbnails\n',
            status=JobStatus.ERROR)
        return
    msg = 'Finished creating large image thumbnails (%d checked or created)' % (
        checkedOrCreated)
    if failedImages:
        msg += ' (%d failed, last failure on item %s)' % (
            failedImages, lastFailed)
    logger.info(msg)
    job = Job().updateJob(job, log=msg + '\n', status=JobStatus.SUCCESS)
Пример #5
0
def onJobUpdate(event):
    """
    Event handler for job update event.

    When a Danesfield job succeeds, advance the workflow.
    When a Danesfield job fails, remove its associated information
    from the workflow manager.
    """
    job = event.info['job']
    params = event.info['params']

    try:
        jobId = job[DanesfieldJobKey.ID]
        stepName = job[DanesfieldJobKey.STEP_NAME]
    except KeyError:
        return

    try:
        # FIXME: Sometimes status is unicode, not int
        status = int(params['status'])
    except (TypeError, KeyError, ValueError):
        return

    workflowManager = DanesfieldWorkflowManager.instance()

    with _onJobUpdateLock:
        # Handle composite steps
        if workflowManager.isCompositeStep(jobId, stepName):
            # Notify workflow manager when a job in a composite step completes
            if status in (JobStatus.SUCCESS, JobStatus.ERROR,
                          JobStatus.CANCELED):
                workflowManager.compositeStepJobCompleted(jobId, stepName)
            # Skip processing until all jobs in a composite step have completed
            if not workflowManager.isCompositeStepComplete(jobId, stepName):
                return
            # Set overall status for composite step
            successful = workflowManager.isCompositeStepSuccessful(
                jobId, stepName)
            status = JobStatus.SUCCESS if successful else JobStatus.ERROR

        if status == JobStatus.SUCCESS:
            # Add standard output from job
            # TODO: Alternatively, could record job model ID and defer
            # log lookup
            # TODO: This currently doesn't support composite steps;
            # only the output
            # from the last job is saved
            job = Job().load(job['_id'], includeLog=True, force=True)
            workflowManager.addStandardOutput(jobId=jobId,
                                              stepName=stepName,
                                              output=job.get('log'))

            workflowManager.stepSucceeded(jobId=jobId, stepName=stepName)

            # Advance workflow asynchronously to avoid affecting
            # finished job in case of error
            events.daemon.trigger(info={
                'jobId': jobId,
                'stepName': stepName,
                'userId': job['userId']
            },
                                  callback=advanceWorkflow)
        elif status in (JobStatus.CANCELED, JobStatus.ERROR):
            workflowManager.stepFailed(jobId=jobId, stepName=stepName)
Пример #6
0
def createThumbnailsJob(job):
    """
    Create thumbnails for all of the large image items.

    :param job: the job object including kwargs which contains:
        spec: an array, each entry of which is the parameter dictionary for
            the model getThumbnail function.
        logInterval: the time in seconds between log messages.  This also
            controls the granularity of cancelling the job.
        concurrent: the number of threads to use.  0 for the number of cpus.
    """
    job = Job().updateJob(
        job, log='Started creating large image thumbnails\n',
        status=JobStatus.RUNNING)
    concurrency = int(job['kwargs'].get('concurrent', 0))
    concurrency = psutil.cpu_count(logical=True) if concurrency < 1 else concurrency
    status = {
        'checked': 0,
        'created': 0,
        'failed': 0,
    }

    spec = job['kwargs']['spec']
    logInterval = float(job['kwargs'].get('logInterval', 10))
    job = Job().updateJob(job, log='Creating thumbnails (%d concurrent)\n' % concurrency)
    nextLogTime = time.time() + logInterval
    tasks = []
    # This could be switched from ThreadPoolExecutor to ProcessPoolExecutor
    # without any other changes.  Doing so would probably improve parallel
    # performance, but may not work reliably under Python 2.x.
    pool = concurrent.futures.ThreadPoolExecutor(max_workers=concurrency)
    try:
        # Get a cursor with the list of images
        items = Item().find({'largeImage.fileId': {'$exists': True}})
        if hasattr(items, 'count'):
            status['items'] = items.count()
        status['specs'] = len(spec)
        nextitem = cursorNextOrNone(items)
        while len(tasks) or nextitem is not None:
            # Create more tasks than we strictly need so if one finishes before
            # we check another will be ready.  This is balanced with not
            # creating too many to avoid excessive memory use.  As such, we
            # can't do a simple iteration over the database cursor, as it will
            # be exhausted before we are done.
            while len(tasks) < concurrency * 4 and nextitem is not None:
                tasks.append(pool.submit(createThumbnailsJobTask, nextitem, spec))
                nextitem = cursorNextOrNone(items)
            # Wait a short time or until the oldest task is complete
            try:
                tasks[0].result(0.1)
            except concurrent.futures.TimeoutError:
                pass
            # Remove completed tasks from our list, adding their results to the
            # status.
            for pos in range(len(tasks) - 1, -1, -1):
                if tasks[pos].done():
                    r = tasks[pos].result()
                    status['created'] += r['created']
                    status['checked'] += r['checked']
                    status['failed'] += r['failed']
                    status['lastFailed'] = r.get('lastFailed', status.get('lastFailed'))
                    tasks[pos:pos + 1] = []
            # Periodically, log the state of the job and check if it was
            # deleted or canceled.
            if time.time() > nextLogTime:
                job, msg = createThumbnailsJobLog(job, status)
                # Check if the job was deleted or canceled; if so, quit
                job = Job().load(id=job['_id'], force=True)
                if not job or job['status'] in (JobStatus.CANCELED, JobStatus.ERROR):
                    cause = {
                        None: 'deleted',
                        JobStatus.CANCELED: 'canceled',
                        JobStatus.ERROR: 'stopped due to error',
                    }[None if not job else job.get('status')]
                    msg = 'Large image thumbnails job %s' % cause
                    logger.info(msg)
                    # Cancel any outstanding tasks.  If they haven't started,
                    # they are discarded.  Those that have started will still
                    # run, though.
                    for task in tasks:
                        task.cancel()
                    return
                nextLogTime = time.time() + logInterval
    except Exception:
        logger.exception('Error with large image create thumbnails job')
        Job().updateJob(
            job, log='Error creating large image thumbnails\n',
            status=JobStatus.ERROR)
        return
    finally:
        # Clean up the task pool asynchronously
        pool.shutdown(False)
    job, msg = createThumbnailsJobLog(job, status, 'Finished: ', JobStatus.SUCCESS)
    logger.info(msg)