[Desktop_printing] CUPS code quality ...
mike at easysw.com
Thu Feb 2 05:35:49 PST 2006
michael meeks wrote:
> Hi there,
> So - Till asked me to write something public about this.
> This is based on a rumor I heard that there was some plan to
> "standardize on the CUPS API".
> My experience of using the CUPS API is that it is badly designed,
> painful to use, synchronous (and likes to take an unpredictable time to
> do any given operation: ms -> minutes) and to top the lot - thread
> unsafe by design.
I'll address these points in reverse:
1. Thread safety; CUPS 1.2 is thread-safe, and threading support
is enabled by default. Previous releases were thread safe if
you stayed away from the convenience functions that used shared
2. Synchronous; only if you use the convenience APIs - we use the
CUPS API as the basis for cupsd, and all of the HTTP and IPP
APIs operate as FSMs so that we can handle multiple connections
asynchronously. Other apps can do this or use threads as they
2a. Variable time; them's the breaks, some people have fast
systems, some slow, some people use local servers, some
remote, some people have 1 printer, some have 10000.
3. Painful to use; well, that's a matter of opinion, but we will
improve the API (or documentation) as needed to better support
apps. Specific examples will help us make improvements...
4. Badly designed; also a matter of opinion, although I heartily
agree that certain parts of the API are showing their age and
could use a redesign. This, unfortunately, is a challenge
with the parallel goal of maintaining backwards compatibility
in the 1.x series, but we welcome feedback in improving the
existing APIs and adding new ones that match developer usage.
> So - of course, we try to push CUPS work into thread(s) anyway since we
> can't have the 'get-list-of-printers' call randomly blocking OO.o for
> ages on random machines, with random N/W configurations. [ we're slow
> enough in general anyway ;-]
You *do* know that you can do a CUPS_GET_PRINTERS request with a
non-blocking HTTP connection to collect the data as you go along,
right? And use requested-attributes to reduce the amount of data
that is processed?
> Let me give one example eg. the OO.o psprint/ code does:
> So the corresponding helper 'cupsGetPPD' method that we use is:
Note that we use per-thread global data in CUPS 1.2, and that is
what you should be basing your evaluation on - CUPS 1.1.x is a
> So if even 1/2 the brain-power that was consumed adding the comment:
> "/* Looping var */" to an 'int i' statement was used researching the
> problems & failures of API design still biting people long afterwards.
Comments like this are not useful. Simple documentation = easier to
follow code (and clearly you are able to follow the CUPS code, even if
you don't like the design...)
> And/or the relevant costs speed of dynamic memory allocation vs. socket
> IPC - this API wouldn't be as broken as it is. [ also note the extensive
> use of static size buffers etc. ;-]
We use static and dynamic buffers when they make sense. Sockets are a
requirement for using IPP over HTTP. As for the API being broken, it
does exactly what you expect - it gets a PPD file for a print queue,
something that isn't quite as simple as you'd expect due to the
existence of printer classes and remote printers, so cupsGetPPD and
cupsGetPPD2 do the additional lookups needed to find the PPD file.
It is synchronous by design, but nothing prevents you from using the
HTTP and IPP APIs to do this asynchronously - you'll just need to
do the IPP queries and then the HTTP GET yourself. Once you have the
temporary filename you can copy it to your own buffer as needed, but
the normal use case (one PPD file open for the currently selected
printer) doesn't need that...
> Of course, what the contract is wrt. this file - whether you get to
> unlink it later at your leisure, or what is being really passed to you
> is (AFAICS) not explicit in the commenting. An opaque handle with
> clearly defined & consistent lifecycle semantics would clearly be
We're not trying to define a whole new object/abstraction interface
here (i.e. no glib or similar frameworks), just trying to get the
The "contract" issue is just a matter of documentation, which will
hopefully be stronger in CUPS 1.2. However, in this case the CUPS
Software Programmers Manual actually provides the "contract" info:
cupsGetPPD() gets a copy of the PPD file for the named printer.
The printer name can be of the form "printer" or "printer at hostname".
You should remove (unlink) the PPD file after you are done using it.
The filename is stored in a static buffer and will be overwritten
with each call to cupsGetPPD().
> Is this symptomatic of a wider problem ? no idea. All I know is my
> experience of using the client code & trying to make it do what is
> necessary has been poor in the extreme: of course, it's entirely
> possible that is just down to my ignorance.
In my experience, too many developers jump to conclusions about
software before asking the people who wrote the software...
> Please don't standardize on it ;-) wrap it, re-write the API from
> scratch [ under eclectic copyright ? ;-] do whatever; but - lets get
> some people expert in API design to put some thought & review into the
> design of this critical API, and lets ensure it's thread safe (or at
> least can be made thread-safe later :-).
There is already an API like this: PAPI. However, you may be
disappointed to discover that PAPI's APIs are all synchronous, with no
possibility of asynchronous operation. PAPI also suffers from the
"least common denominator" syndrome, but that will likely improve over
Michael Sweet, Easy Software Products mike at easysw dot com
Internet Printing and Publishing Software http://www.easysw.com
More information about the Printing-summit