示例#1
0
/* Wait for & get next page queue entry */
gx_page_queue_entry_t *		/* removed entry */
gx_page_queue_start_dequeue(
			       gx_page_queue_t * queue	/* page queue to retrieve from */
)
{
    gx_semaphore_wait(queue->render_req_sema);
    queue->dequeue_in_progress = true;
    return gx_page_queue_remove_first(queue);
}
示例#2
0
/* Wait for a single page to finish rendering (if any pending) */
int				/* rets 0 if no pages were waiting for rendering, 1 if actually waited */
gx_page_queue_wait_one_page(
			       gx_page_queue_t * queue	/* queue to wait on */
)
{
    int code;

    gx_monitor_enter(queue->monitor);
    if (!queue->entry_count && !queue->dequeue_in_progress) {
	code = 0;
	gx_monitor_leave(queue->monitor);
    } else {
	/* request acknowledgement on render done */
	queue->enable_render_done_signal = true;

	/* exit monitor & wait for acknowlegement */
	gx_monitor_leave(queue->monitor);
	gx_semaphore_wait(queue->render_done_sema);
	code = 1;
    }
    return code;
}
示例#3
0
static gsicc_link_t*
gsicc_findcachelink(gsicc_hashlink_t hash, gsicc_link_cache_t *icc_link_cache, bool includes_proof)
{
    gsicc_link_t *curr, *prev;
    int64_t hashcode = hash.link_hashcode;

    /* Look through the cache for the hashcode */
    gx_monitor_enter(icc_link_cache->lock);

    /* List scanning is fast, so we scan the entire list, this includes   */
    /* links that are currently unused, but still in the cache (zero_ref) */
    curr = icc_link_cache->head;
    prev = NULL;

    while (curr != NULL ) {
        if (curr->hashcode.link_hashcode == hashcode && includes_proof == curr->includes_softproof) {
	    /* move this one to the front of the list hoping we will use it again soon */
	    if (prev != NULL) {		/* if prev == NULL, curr is already the head */
		prev->next = curr->next;
		curr->next = icc_link_cache->head;
		icc_link_cache->head = curr;
	    }
	    curr->ref_count++;		/* bump the ref_count since we will be using this one */
	    while (curr->valid == false) {
		curr->num_waiting++;
	    	gx_monitor_leave(icc_link_cache->lock);
		gx_semaphore_wait(curr->wait);
    		gx_monitor_enter(icc_link_cache->lock);	/* re-enter breifly */
	    }
	    gx_monitor_leave(icc_link_cache->lock);
            return(curr);	/* success */
        }
	prev = curr;
        curr = curr->next;
    }
    gx_monitor_leave(icc_link_cache->lock);
    return(NULL);
}
示例#4
0
/*
 * Copy the raster data from the completed thread to the caller's
 * device (the main thread)
 * Return 0 if OK, < 0 is the error code from the thread 
 *
 * After swapping the pointers, start up the completed thread with the
 * next band remaining to do (if any)
 */
static int
clist_get_band_from_thread(gx_device *dev, int band_needed)
{
    gx_device_clist *cldev = (gx_device_clist *)dev;
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
    gx_device_clist_reader *crdev = &cldev->reader;
    int i, next_band, code = 0;
    int thread_index = crdev->curr_render_thread;
    clist_render_thread_control_t *thread = &(crdev->render_threads[thread_index]);
    gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;
    int band_height = crdev->page_info.band_params.BandHeight;
    int band_count = cdev->nbands;
    byte *tmp;			/* for swapping data areas */

    /* We expect that the thread needed will be the 'current' thread */
    if (thread->band != band_needed) {
	int band = band_needed;

	/* Probably we went in the wrong direction, so let the threads */
	/* all complete, then restart them in the opposite direction   */
	/* If the caller is 'bouncing around' we may end up back here, */
	/* but that is a VERY rare case (we haven't seen it yet).      */
	for (i=0; i < crdev->num_render_threads; i++) {
	    clist_render_thread_control_t *thread = &(crdev->render_threads[i]);

	    if (thread->status == RENDER_THREAD_BUSY)
		gx_semaphore_wait(thread->sema_this);
	}
	crdev->thread_lookahead_direction *= -1;
	/* Loop creating the devices and semaphores for each thread, then start them */
	for (i=0; (i < crdev->num_render_threads) && (band >= 0) && (band < band_count);
		i++, band += crdev->thread_lookahead_direction) {
	    thread = &(crdev->render_threads[i]);

	    thread->band = -1;		/* a value that won't match any valid band */
	    /* Start thread 'i' to do band */
	    if ((code = clist_start_render_thread(dev, i, band)) < 0)
		break;
	}
	crdev->curr_render_thread = thread_index = 0;
	thread = &(crdev->render_threads[0]);
    }
    /* Wait for this thread */
    gx_semaphore_wait(thread->sema_this);
    if (thread->status < 0)
	return thread->status;		/* FAIL */

    /* Swap the data areas to avoid the copy */
    tmp = cdev->data;
    cdev->data = thread_cdev->data;
    thread_cdev->data = tmp;
    thread->status = RENDER_THREAD_IDLE;	/* the data is no longer valid */
    thread->band = -1;
    /* Update the bounds for this band */
    cdev->ymin =  band_needed * band_height;
    cdev->ymax =  cdev->ymin + band_height;
    if (cdev->ymax > dev->height)
	cdev->ymax = dev->height;

    /* If we are not at the final band, start up this thread with the next one to do */
    next_band = band_needed + (crdev->num_render_threads * crdev->thread_lookahead_direction);
    if (next_band >= 0 && next_band < band_count)
	code = clist_start_render_thread(dev, thread_index, next_band);
    /* bump the 'curr' to the next thread */
    crdev->curr_render_thread = crdev->curr_render_thread == crdev->num_render_threads - 1 ?
		0 : crdev->curr_render_thread + 1;

    return code;
}
示例#5
0
void
clist_teardown_render_threads(gx_device *dev)
{
    gx_device_clist *cldev = (gx_device_clist *)dev;
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
    gx_device_clist_reader *crdev = &cldev->reader;
    gs_memory_t *mem = cdev->bandlist_memory;
    int i;

    if (crdev->render_threads != NULL) {

	/* Wait for each thread to finish then free its memory */
	for (i = (crdev->num_render_threads - 1); i >= 0; i--) {
	    clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
	    gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;

	    if (thread->status == RENDER_THREAD_BUSY)
		gx_semaphore_wait(thread->sema_this);
	    /* Free control semaphores */
	    gx_semaphore_free(thread->sema_group);
	    gx_semaphore_free(thread->sema_this);
	    /* destroy the thread's buffer device */
	    thread_cdev->buf_procs.destroy_buf_device(thread->bdev);
	    /*
	     * Free the BufferSpace, close the band files 
	     * Note that the BufferSpace is freed using 'ppdev->buf' so the 'data'
	     * pointer doesn't need to be the one that the thread started with
	     */
	    /* Close the file handles, but don't delete (unlink) the files */
	    thread_cdev->page_info.io_procs->fclose(thread_cdev->page_bfile, thread_cdev->page_bfname, false);
	    thread_cdev->page_info.io_procs->fclose(thread_cdev->page_cfile, thread_cdev->page_cfname, false);
	    thread_cdev->do_not_open_or_close_bandfiles = true;	/* we already closed the files */
	    gdev_prn_free_memory((gx_device *)thread_cdev);
	    /* Free the device copy this thread used */
	    gs_free_object(thread->memory, thread_cdev, "clist_teardown_render_threads");
#ifdef DEBUG
	    if (gs_debug[':'])
		dprintf2("%% Thread %d total usertime=%ld msec\n", i, thread->cputime);
	    dprintf1("\nthread: %d ending memory state...\n", i);
	    gs_memory_chunk_dump_memory(thread->memory); 
	    dprintf("                                    memory dump done.\n");
#endif

	    gs_memory_chunk_release(thread->memory); 
	}
	cdev->data = crdev->main_thread_data;	/* restore the pointer for writing */
	gs_free_object(mem, crdev->render_threads, "clist_teardown_render_threads");
	crdev->render_threads = NULL;

	/* Now re-open the clist temp files so we can write to them */
	if (cdev->page_cfile == NULL) {
	    char fmode[4];

	    strcpy(fmode, "a+");	/* file already exists and we want to re-use it */
	    strncat(fmode, gp_fmode_binary_suffix, 1);
	    cdev->page_info.io_procs->fopen(cdev->page_cfname, fmode, &cdev->page_cfile,
				mem, cdev->bandlist_memory, true);
	    cdev->page_info.io_procs->fseek(cdev->page_cfile, 0, SEEK_SET, cdev->page_cfname);
	    cdev->page_info.io_procs->fopen(cdev->page_bfname, fmode, &cdev->page_bfile,
				mem, cdev->bandlist_memory, false);
	    cdev->page_info.io_procs->fseek(cdev->page_bfile, 0, SEEK_SET, cdev->page_bfname);
	}
    }
}
示例#6
0
/*
 * Open this printer device in ASYNC (overlapped) mode.
 * This routine must always called by the concrete device's xx_open routine
 * in lieu of gdev_prn_open.
 */
int
gdev_prn_async_write_open(gx_device_printer * pwdev, int max_raster,
                          int min_band_height, int max_src_image_row)
{
    gx_device *const pdev = (gx_device *) pwdev;
    int code;
    bool writer_is_open = false;
    gx_device_clist_writer *const pcwdev =
        &((gx_device_clist *) pwdev)->writer;
    gx_device_clist_reader *pcrdev = 0;
    gx_device_printer *prdev = 0;
    gs_memory_t *render_memory = 0;	/* renderer's mem allocator */

    pwdev->page_queue = 0;
    pwdev->bandlist_memory = 0;
    pwdev->async_renderer = 0;

    /* allocate & init render memory */
    /* The big memory consumers are: */
    /* - the buffer used to read images from the command list */
    /* - buffer used by gx_real_default_strip_copy_rop() */
    /* - line pointer tables for memory devices used in plane extraction */
    /* - the halftone cache */
    /* - the band rendering buffer */
    /* The * 2's in the next statement are a ****** HACK ****** to deal with */
    /* sandbars in the memory manager. */
    if ((code = alloc_render_memory(&render_memory,
            pwdev->memory->non_gc_memory, RendererAllocationOverheadBytes + max_raster
                                    /* the first * 2 is not a hack */
                   + (max_raster + sizeof(void *) * 2) * min_band_height
                   + max_src_image_row + gx_ht_cache_default_bits_size() * 2)) < 0)
             goto open_err;

    /* Alloc & init bandlist allocators */
    /* Bandlist mem is threadsafe & common to rdr/wtr, so it's used */
    /* for page queue & cmd list buffers. */
    if ((code = alloc_bandlist_memory
         (&pwdev->bandlist_memory, pwdev->memory->non_gc_memory)) < 0)
        goto open_err;

    /* Dictate banding parameters for both renderer & writer */
    /* Protect from user change, since user changing these won't be */
    /* detected, ergo the necessary close/reallocate/open wouldn't happen. */
    pwdev->space_params.banding_type = BandingAlways;
    pwdev->space_params.params_are_read_only = true;

    /* Make a copy of device for use as rendering device b4 opening writer */
    code = gs_copydevice((gx_device **) & prdev, pdev, render_memory);
    pcrdev = &((gx_device_clist *) prdev)->reader;
    if (code < 0)
        goto open_err;

    /* -------------- Open cmd list WRITER instance of device ------- */
    /* --------------------------------------------------------------- */
    /* This is wrong, because it causes the same thing in the renderer */
    pwdev->OpenOutputFile = 0;	/* Don't open output file in writer */

    /* Hack: set this vector to tell gdev_prn_open to allocate for async rendering */
    pwdev->free_up_bandlist_memory = &gdev_prn_async_write_free_up_bandlist_memory;

    /* prevent clist writer from queuing path graphics & force it to split images */
    pwdev->clist_disable_mask |= clist_disable_fill_path |
        clist_disable_stroke_path | clist_disable_complex_clip |
        clist_disable_nonrect_hl_image | clist_disable_pass_thru_params;

    if ((code = gdev_prn_open(pdev)) >= 0) {
        writer_is_open = true;

        /* set up constant async-specific fields in device */
        reinit_printer_into_printera(pwdev);

        /* keep ptr to renderer device */
        pwdev->async_renderer = prdev;

        /* Allocate the page queue, then initialize it */
        /* Use bandlist memory since it's shared between rdr & wtr */
        if ((pwdev->page_queue = gx_page_queue_alloc(pwdev->bandlist_memory)) == 0)
            code = gs_note_error(gs_error_VMerror);
        else
            /* Allocate from clist allocator since it is thread-safe */
            code = gx_page_queue_init(pwdev->page_queue, pwdev->bandlist_memory);
    }
    /* ------------ Open cmd list RENDERER instance of device ------- */
    /* --------------------------------------------------------------- */
    if (code >= 0) {
        gx_semaphore_t *open_semaphore;

        /* Force writer's actual band params into reader's requested params */
        prdev->space_params.band = pcwdev->page_info.band_params;

        /* copydevice has already set up prdev->memory = render_memory */
        /* prdev->bandlist_memory = pwdev->bandlist_memory; */
        prdev->buffer_memory = prdev->memory;

        /* enable renderer to accept changes to params computed by writer */
        prdev->space_params.params_are_read_only = false;

        /* page queue is common to both devices */
        prdev->page_queue = pwdev->page_queue;

        /* Start renderer thread & wait for its successful open of device */
        if (!(open_semaphore = gx_semaphore_alloc(prdev->memory)))
            code = gs_note_error(gs_error_VMerror);
        else {
            gdev_prn_start_render_params thread_params;

            thread_params.writer_device = pwdev;
            thread_params.open_semaphore = open_semaphore;
            thread_params.open_code = 0;
            code = (*pwdev->printer_procs.start_render_thread)
                (&thread_params);
            if (code >= 0)
                gx_semaphore_wait(open_semaphore);
            code = thread_params.open_code;
            gx_semaphore_free(open_semaphore);
        }
    }
    /* ----- Set the recovery procedure for the mem allocator ----- */
    if (code >= 0) {
        gs_memory_retrying_set_recover(
                (gs_memory_retrying_t *)pwdev->memory->non_gc_memory,
                prna_mem_recover,
                (void *)pcwdev
            );
    }
    /* --------------------- Wrap up --------------------------------- */
    /* --------------------------------------------------------------- */
    if (code < 0) {
open_err:
        /* error mop-up */
        if (render_memory && !prdev)
            free_render_memory(render_memory);

        gdev_prn_dealloc(pwdev);
        if (writer_is_open) {
            gdev_prn_close(pdev);
            pwdev->free_up_bandlist_memory = 0;
        }
    }
    return code;
}
示例#7
0
/*
 * Copy the raster data from the completed thread to the caller's
 * device (the main thread)
 * Return 0 if OK, < 0 is the error code from the thread
 *
 * After swapping the pointers, start up the completed thread with the
 * next band remaining to do (if any)
 */
static int
clist_get_band_from_thread(gx_device *dev, int band_needed, gx_process_page_options_t *options)
{
    gx_device_clist *cldev = (gx_device_clist *)dev;
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
    gx_device_clist_reader *crdev = &cldev->reader;
    int i, code = 0;
    int thread_index = crdev->curr_render_thread;
    clist_render_thread_control_t *thread = &(crdev->render_threads[thread_index]);
    gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;
    int band_height = crdev->page_info.band_params.BandHeight;
    int band_count = cdev->nbands;
    byte *tmp;                  /* for swapping data areas */

    /* We expect that the thread needed will be the 'current' thread */
    if (thread->band != band_needed) {
        int band = band_needed;

        /* Probably we went in the wrong direction, so let the threads */
        /* all complete, then restart them in the opposite direction   */
        /* If the caller is 'bouncing around' we may end up back here, */
        /* but that is a VERY rare case (we haven't seen it yet).      */
        for (i=0; i < crdev->num_render_threads; i++) {
            clist_render_thread_control_t *thread = &(crdev->render_threads[i]);

            if (thread->status == THREAD_BUSY)
                gx_semaphore_wait(thread->sema_this);
        }
        crdev->thread_lookahead_direction *= -1;      /* reverse direction (but may be overruled below) */
        if (band_needed == band_count-1)
            crdev->thread_lookahead_direction = -1;   /* assume backwards if we are asking for the last band */
        if (band_needed == 0)
            crdev->thread_lookahead_direction = 1;    /* force forward if we are looking for band 0 */
        /* Loop creating the devices and semaphores for each thread, then start them */
        for (i=0; (i < crdev->num_render_threads) && (band >= 0) && (band < band_count);
                i++, band += crdev->thread_lookahead_direction) {
            thread = &(crdev->render_threads[i]);
            thread->band = -1;          /* a value that won't match any valid band */
            /* Start thread 'i' to do band */
            if ((code = clist_start_render_thread(dev, i, band)) < 0)
                break;
        }
        crdev->next_band = i;			/* may be < 0 or == band_count, but that is handled later */
        crdev->curr_render_thread = thread_index = 0;
        thread = &(crdev->render_threads[0]);
        thread_cdev = (gx_device_clist_common *)thread->cdev;
    }
    /* Wait for this thread */
    gx_semaphore_wait(thread->sema_this);
    gp_thread_finish(thread->thread);
    thread->thread = NULL;
    if (thread->status == THREAD_ERROR)
        return gs_error_unknownerror;          /* FAIL */

    if (options && options->output_fn) {
        code = options->output_fn(options->arg, dev, thread->buffer);
        if (code < 0)
            return code;
    }

    /* Swap the data areas to avoid the copy */
    tmp = cdev->data;
    cdev->data = thread_cdev->data;
    thread_cdev->data = tmp;
    thread->status = THREAD_IDLE;        /* the data is no longer valid */
    thread->band = -1;
    /* Update the bounds for this band */
    cdev->ymin =  band_needed * band_height;
    cdev->ymax =  cdev->ymin + band_height;
    if (cdev->ymax > dev->height)
        cdev->ymax = dev->height;

    if (crdev->next_band >= 0 && crdev->next_band < band_count) {
        code = clist_start_render_thread(dev, thread_index, crdev->next_band);
        crdev->next_band += crdev->thread_lookahead_direction;
    }
    /* bump the 'curr' to the next thread */
    crdev->curr_render_thread = crdev->curr_render_thread == crdev->num_render_threads - 1 ?
                0 : crdev->curr_render_thread + 1;

    return code;
}
示例#8
0
void
clist_teardown_render_threads(gx_device *dev)
{
    gx_device_clist *cldev = (gx_device_clist *)dev;
    gx_device_clist_common *cdev = (gx_device_clist_common *)dev;
    gx_device_clist_reader *crdev = &cldev->reader;
    gs_memory_t *mem = cdev->bandlist_memory;
    int i;

    if (crdev->render_threads != NULL) {
        /* Wait for each thread to finish then free its memory */
        for (i = (crdev->num_render_threads - 1); i >= 0; i--) {
            clist_render_thread_control_t *thread = &(crdev->render_threads[i]);
            gx_device_clist_common *thread_cdev = (gx_device_clist_common *)thread->cdev;

            if (thread->status == THREAD_BUSY)
                gx_semaphore_wait(thread->sema_this);
            /* Free control semaphores */
            gx_semaphore_free(thread->sema_group);
            gx_semaphore_free(thread->sema_this);
            /* destroy the thread's buffer device */
            thread_cdev->buf_procs.destroy_buf_device(thread->bdev);

            if (thread->options) {
                if (thread->options->free_buffer_fn && thread->buffer) {
                    thread->options->free_buffer_fn(thread->options->arg, dev, thread->memory, thread->buffer);
                    thread->buffer = NULL;
                }
                thread->options = NULL;
            }

            /* before freeing this device's memory, swap with cdev if it was the main_thread_data */
            if (thread_cdev->data == crdev->main_thread_data) {
                thread_cdev->data = cdev->data;
                cdev->data = crdev->main_thread_data;
            }
#ifdef DEBUG
            if (gs_debug[':'])
                dmprintf2(thread->memory, "%% Thread %d total usertime=%ld msec\n", i, thread->cputime);
            dmprintf1(thread->memory, "\nThread %d ", i);
#endif
            teardown_device_and_mem_for_thread((gx_device *)thread_cdev, thread->thread, false);
        }
        gs_free_object(mem, crdev->render_threads, "clist_teardown_render_threads");
        crdev->render_threads = NULL;

        /* Now re-open the clist temp files so we can write to them */
        if (cdev->page_info.cfile == NULL) {
            char fmode[4];

            strcpy(fmode, "a+");        /* file already exists and we want to re-use it */
            strncat(fmode, gp_fmode_binary_suffix, 1);
            cdev->page_info.io_procs->fopen(cdev->page_info.cfname, fmode, &cdev->page_info.cfile,
                                mem, cdev->bandlist_memory, true);
            cdev->page_info.io_procs->fseek(cdev->page_info.cfile, 0, SEEK_SET, cdev->page_info.cfname);
            cdev->page_info.io_procs->fopen(cdev->page_info.bfname, fmode, &cdev->page_info.bfile,
                                mem, cdev->bandlist_memory, false);
            cdev->page_info.io_procs->fseek(cdev->page_info.bfile, 0, SEEK_SET, cdev->page_info.bfname);
        }
    }
}
示例#9
0
gsicc_link_t*
gsicc_get_link_profile(gs_imager_state *pis, gx_device *dev,
                       cmm_profile_t *gs_input_profile,
                       cmm_profile_t *gs_output_profile,
                       gsicc_rendering_param_t *rendering_params,
                       gs_memory_t *memory, bool include_softproof)
{
    gsicc_hashlink_t hash;
    gsicc_link_t *link, *found_link;
    gcmmhlink_t link_handle = NULL;
    void **contextptr = NULL;
    gsicc_manager_t *icc_manager = pis->icc_manager;
    gsicc_link_cache_t *icc_link_cache = pis->icc_link_cache;
    gs_memory_t *cache_mem = pis->icc_link_cache->memory;

    gcmmhprofile_t *cms_input_profile;
    gcmmhprofile_t *cms_output_profile;

    /* First compute the hash code for the incoming case */
    /* If the output color space is NULL we will use the device profile for the output color space */
    gsicc_compute_linkhash(icc_manager, dev, gs_input_profile, gs_output_profile,
                            rendering_params, &hash);

    /* Check the cache for a hit.  Need to check if softproofing was used */
    found_link = gsicc_findcachelink(hash, icc_link_cache, include_softproof);

    /* Got a hit, return link (ref_count for the link was already bumped */
    if (found_link != NULL)
        return(found_link);  /* TO FIX: We are really not going to want to have the members
                          of this object visible outside gsiccmange */

    /* If not, then lets create a new one if there is room or return NULL */
    /* Caller will need to try later */

    /* First see if we can add a link */
    /* TODO: this should be based on memory usage, not just num_links */
    gx_monitor_enter(icc_link_cache->lock);
    while (icc_link_cache->num_links >= ICC_CACHE_MAXLINKS) {
        /* If not, see if there is anything we can remove from cache. */
	while ((link = gsicc_find_zeroref_cache(icc_link_cache)) == NULL) {
	    icc_link_cache->num_waiting++;
	    /* safe to unlock since above will make sure semaphore is signalled */
	    gx_monitor_leave(icc_link_cache->lock);
	    /* we get signalled (released from wait) when a link goes to zero ref */
	    gx_semaphore_wait(icc_link_cache->wait);

	    /* repeat the findcachelink to see if some other thread has	*/
	    /*already started building the link	we need			*/
	    found_link = gsicc_findcachelink(hash, icc_link_cache, include_softproof);

	    /* Got a hit, return link (ref_count for the link was already bumped */
	    if (found_link != NULL)
		return(found_link);  /* TO FIX: We are really not going to want to have the members
				  of this object visible outside gsiccmange */

	    gx_monitor_enter(icc_link_cache->lock);	    /* restore the lock */
	    /* we will re-test the num_links above while locked to insure */
	    /* that some other thread didn't grab the slot and max us out */
	}
	/* Remove the zero ref_count link profile we found.		*/
	/* Even if we remove this link, we may still be maxed out so	*/
	/* the outermost 'while' will check to make sure some other	*/
	/* thread did not grab the one we remove.			*/
	gsicc_remove_link(link, cache_mem);
        icc_link_cache->num_links--;
    }
    /* insert an empty link that we will reserve so we */
    /* can unlock while building the link contents     */
    link = gsicc_alloc_link(cache_mem->stable_memory, hash);
    link->icc_link_cache = icc_link_cache;
    link->next = icc_link_cache->head;
    icc_link_cache->head = link;
    icc_link_cache->num_links++;
    gx_monitor_leave(icc_link_cache->lock);	/* now that we own this link we can release */
					/* the lock since it is not valid */

    /* Now compute the link contents */
    cms_input_profile = gs_input_profile->profile_handle;
    if (cms_input_profile == NULL) {
        if (gs_input_profile->buffer != NULL) {
            cms_input_profile =
                gsicc_get_profile_handle_buffer(gs_input_profile->buffer,
                                                gs_input_profile->buffer_size);
            gs_input_profile->profile_handle = cms_input_profile;
        } else {
            /* See if we have a clist device pointer. */
            if ( gs_input_profile->dev != NULL ) {
                /* ICC profile should be in clist. This is
                   the first call to it.  Note that the profiles are not
                   really shared amongst threads like the links are.  Hence
                   the memory is for the local thread's chunk */
                cms_input_profile =
                    gsicc_get_profile_handle_clist(gs_input_profile,
                                                   gs_input_profile->memory);
                gs_input_profile->profile_handle = cms_input_profile;
            } else {
                /* Cant create the link.  No profile present,
                   nor any defaults to use for this.  Really
                   need to throw an error for this case. */
		gsicc_remove_link(link, cache_mem);
                icc_link_cache->num_links--;
                return(NULL);
            }
        }
    }

    cms_output_profile = gs_output_profile->profile_handle;
    if (cms_output_profile == NULL) {
        if (gs_output_profile->buffer != NULL) {
            cms_input_profile =
                gsicc_get_profile_handle_buffer(gs_input_profile->buffer,
                                                gs_input_profile->buffer_size);
            gs_output_profile->profile_handle = cms_output_profile;
        } else {
              /* See if we have a clist device pointer. */
            if ( gs_output_profile->dev != NULL ) {
                /* ICC profile should be in clist. This is
                   the first call to it. */
                cms_output_profile =
                    gsicc_get_profile_handle_clist(gs_output_profile,
                                                   gs_output_profile->memory);
                gs_output_profile->profile_handle = cms_output_profile;
            } else {
                /* Cant create the link.  No profile present,
                   nor any defaults to use for this.  Really
                   need to throw an error for this case. */
		gsicc_remove_link(link, cache_mem);
                icc_link_cache->num_links--;
                return(NULL);
            }
        }
    }
    link_handle = gscms_get_link(cms_input_profile, cms_output_profile,
                                    rendering_params);
    if (link_handle != NULL) {
	gsicc_set_link_data(link, link_handle, contextptr, hash, icc_link_cache->lock);
    } else {
	gsicc_remove_link(link, cache_mem);
        icc_link_cache->num_links--;
        return(NULL);
    }
    return(link);
}