source: geovis/trunk/RenderServer.cpp @ 6673

Last change on this file since 6673 was 6404, checked in by ldelgass, 8 years ago

Add optional idle timeout to geovis server

File size: 12.6 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2013-2015  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cstdio>
9#include <cstring>
10#include <cstdlib>
11#include <cerrno>
12#include <unistd.h>
13#include <signal.h>
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <fcntl.h>
17#include <sys/time.h>
18#include <sys/times.h>
19
20#include <string>
21#include <sstream>
22
23#include <tcl.h>
24
25#include "Trace.h"
26#include "FileUtil.h"
27#include "ReadBuffer.h"
28#include "RenderServer.h"
29#include "RendererCmd.h"
30#include "Renderer.h"
31#include "Stats.h"
32#include "PPMWriter.h"
33#include "TGAWriter.h"
34#ifdef USE_THREADS
35#include <pthread.h>
36#include "ResponseQueue.h"
37#ifdef USE_READ_THREAD
38#include "CommandQueue.h"
39#endif
40#endif
41
42using namespace GeoVis;
43
44Stats GeoVis::g_stats;
45
46int GeoVis::g_statsFile = -1; ///< Stats output file descriptor.
47int GeoVis::g_fdIn = STDIN_FILENO; ///< Input file descriptor
48int GeoVis::g_fdOut = STDOUT_FILENO; ///< Output file descriptor
49FILE *GeoVis::g_fOut = NULL; ///< Output file handle
50FILE *GeoVis::g_fLog = NULL; ///< Trace logging file handle
51Renderer *GeoVis::g_renderer = NULL; ///< Main render worker
52ReadBuffer *GeoVis::g_inBufPtr = NULL; ///< Socket read buffer
53#ifdef USE_THREADS
54ResponseQueue *GeoVis::g_outQueue = NULL;
55#ifdef USE_READER_THREAD
56CommandQueue *GeoVis::g_inQueue = NULL;
57#endif
58#endif
59
60static int
61queueViewpoint()
62{
63    osgEarth::Viewpoint view = g_renderer->getViewpoint();
64
65    std::ostringstream oss;
66    size_t len = 0;
67    oss << "nv>camera get "
68        << view.focalPoint()->x() << " "
69        << view.focalPoint()->y() << " "
70        << view.focalPoint()->z() << " "
71        << view.heading()->getValue() << " "
72        << view.pitch()->getValue() << " "
73        << view.range()->getValue()
74        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getHorizInitString()) << "}"
75        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getVertInitString()) << "}"
76        << "\n";
77    std::string ostr = oss.str();
78    len = ostr.size();
79#ifdef USE_THREADS
80    queueResponse(ostr.c_str(), len, Response::VOLATILE);
81#else
82    ssize_t bytesWritten = SocketWrite(ostr.c_str(), len);
83
84    if (bytesWritten < 0) {
85        return TCL_ERROR;
86    }
87#endif /*USE_THREADS*/
88    return TCL_OK;
89}
90
91#ifdef USE_THREADS
92static void
93queueFrame(ResponseQueue *queue, const unsigned char *imgData)
94{
95#ifdef DEBUG_WRITE_FRAME_FILE
96
97#ifdef RENDER_TARGA
98    writeTGAFile("/tmp/frame.tga",
99                 imgData,
100                 g_renderer->getWindowWidth(),
101                 g_renderer->getWindowHeight(),
102                 TARGA_BYTES_PER_PIXEL);
103#else
104    writeTGAFile("/tmp/frame.tga",
105                 imgData,
106                 g_renderer->getWindowWidth(),
107                 g_renderer->getWindowHeight(),
108                 TARGA_BYTES_PER_PIXEL,
109                 true);
110#endif  /*RENDER_TARGA*/
111
112#else
113 
114#ifdef RENDER_TARGA
115    queueTGA(queue, "nv>image -type image -bytes",
116             imgData,
117             g_renderer->getWindowWidth(),
118             g_renderer->getWindowHeight(),
119             TARGA_BYTES_PER_PIXEL);
120#else
121    char cmd[256];
122    snprintf(cmd, sizeof(cmd), "nv>image -type image -token %lu -bytes", g_stats.nCommands);
123    queuePPM(queue, cmd,
124             imgData,
125             g_renderer->getWindowWidth(),
126             g_renderer->getWindowHeight());
127#endif  /*RENDER_TARGA*/
128#endif  /*DEBUG_WRITE_FRAME_FILE*/
129}
130
131#else
132
133static void
134writeFrame(int fd, const unsigned char *imgData)
135{
136#ifdef DEBUG_WRITE_FRAME_FILE
137
138#ifdef RENDER_TARGA
139    writeTGAFile("/tmp/frame.tga",
140                 imgData,
141                 g_renderer->getWindowWidth(),
142                 g_renderer->getWindowHeight(),
143                 TARGA_BYTES_PER_PIXEL);
144#else
145    writeTGAFile("/tmp/frame.tga",
146                 imgData,
147                 g_renderer->getWindowWidth(),
148                 g_renderer->getWindowHeight(),
149                 TARGA_BYTES_PER_PIXEL,
150                 true);
151#endif  /*RENDER_TARGA*/
152
153#else
154
155#ifdef RENDER_TARGA
156    writeTGA(fd, "nv>image -type image -bytes",
157             imgData,
158             g_renderer->getWindowWidth(),
159             g_renderer->getWindowHeight(),
160             TARGA_BYTES_PER_PIXEL);
161#else
162    writePPM(fd, "nv>image -type image -bytes",
163             imgData,
164             g_renderer->getWindowWidth(),
165             g_renderer->getWindowHeight());
166#endif  /*RENDER_TARGA*/
167#endif  /*DEBUG_WRITE_FRAME_FILE*/
168}
169#endif /*USE_THREADS*/
170
171static int
172sendAck()
173{
174    std::ostringstream oss;
175    oss << "nv>ok -token " << g_stats.nCommands << "\n";
176    std::string ostr = oss.str();
177    int nBytes = ostr.length();
178
179    TRACE("Sending OK for commands through %lu", g_stats.nCommands);
180#ifdef USE_THREADS
181    queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::OK);
182#else
183    if (write(g_fdOut, ostr.c_str(), nBytes) < 0) {
184        ERROR("write failed: %s", strerror(errno));
185        return -1;
186    }
187#endif
188    return 0;
189}
190
191static void
192initService()
193{
194    g_fOut = fdopen(g_fdOut, "w");
195    // If running without socket, use stdout for debugging
196    if (g_fOut == NULL && g_fdOut != STDOUT_FILENO) {
197        g_fdOut = STDOUT_FILENO;
198        g_fOut = fdopen(g_fdOut, "w");
199    }
200
201    const char *user = getenv("USER");
202    char *logName = NULL;
203    int logNameLen = 0;
204
205    if (user == NULL) {
206        logNameLen = 19+1;
207        logName = (char *)calloc(logNameLen, sizeof(char));
208        strncpy(logName, "/tmp/geovis_log.txt", logNameLen);
209    } else {
210        logNameLen = 16+strlen(user)+4+1;
211        logName = (char *)calloc(logNameLen, sizeof(char));
212        strncpy(logName, "/tmp/geovis_log_", logNameLen);
213        strncat(logName, user, strlen(user));
214        strncat(logName, ".txt", 4);
215    }
216
217    // open log and map stderr to log file
218    g_fLog = fopen(logName, "w");
219    dup2(fileno(g_fLog), STDERR_FILENO);
220    // If we are writing to socket, map stdout to log
221    if (g_fdOut != STDOUT_FILENO) {
222        dup2(fileno(g_fLog), STDOUT_FILENO);
223    }
224
225    fflush(stdout);
226
227    // clean up malloc'd memory
228    if (logName != NULL) {
229        free(logName);
230    }
231}
232
233static void
234exitService(int code)
235{
236    TRACE("Enter");
237
238    serverStats(g_stats, code);
239
240    // close log file
241    if (g_fLog != NULL) {
242        fclose(g_fLog);
243        g_fLog = NULL;
244    }
245}
246
247#ifdef USE_THREADS
248
249#ifdef USE_READ_THREAD
250static void *
251readerThread(void *clientData)
252{
253    Tcl_Interp *interp = (Tcl_Interp *)clientData;
254
255    TRACE("Starting reader thread");
256
257    queueCommands(interp, NULL, g_inBufPtr);
258
259    return NULL;
260}
261#endif
262
263static void *
264writerThread(void *clientData)
265{
266    ResponseQueue *queue = (ResponseQueue *)clientData;
267
268    TRACE("Starting writer thread");
269    for (;;) {
270        Response *response = queue->dequeue();
271        if (response == NULL)
272            continue;
273        if (fwrite(response->message(), sizeof(unsigned char), response->length(),
274                   g_fOut) != response->length()) {
275            ERROR("short write while trying to write %ld bytes",
276                  response->length());
277        }
278        fflush(g_fOut);
279#ifdef TRACE_RESPONSE_QUEUE
280        TRACE("Wrote response of type %d", response->type());
281#endif
282        delete response;
283        if (feof(g_fOut))
284            break;
285    }   
286    return NULL;
287}
288
289#endif  /*USE_THREADS*/
290
291int
292main(int argc, char *argv[])
293{
294    // Ignore SIGPIPE.  **Is this needed? **
295    signal(SIGPIPE, SIG_IGN);
296
297    std::string resourcePath;
298    double maxbitrate = 1.0e8;
299    long idleTimeout = -1L;
300    while (1) {
301        int c = getopt(argc, argv, "r:p:i:o:t:");
302        if (c == -1) {
303            break;
304        }
305        switch (c) {
306        case 'p':
307            resourcePath = optarg;
308            break;
309        case 'r':
310            // Option is in kbps
311            maxbitrate = 1000.0 * (double)atol(optarg);
312            break;
313        case 'i': {
314            int fd = atoi(optarg);
315            if (fd >=0 && fd < 5) {
316                g_fdIn = fd;
317            }
318        }
319            break;
320        case 'o': {
321            int fd = atoi(optarg);
322            if (fd >=0 && fd < 5) {
323                g_fdOut = fd;
324            }
325        }
326            break;
327        case 't':
328            // Idle timeout in seconds
329            idleTimeout = atol(optarg);
330            break;
331        case '?':
332            break;
333        default:
334            return 1;
335        }
336    }
337
338    initService();
339    initLog();
340
341    memset(&g_stats, 0, sizeof(g_stats));
342    gettimeofday(&g_stats.start, NULL);
343
344    TRACE("Starting GeoVis Server");
345
346    // Sanity check: log descriptor can't be used for client IO
347    if (fileno(g_fLog) == g_fdIn) {
348        ERROR("Invalid input file descriptor");
349        return 1;
350    }
351    if (fileno(g_fLog) == g_fdOut) {
352        ERROR("Invalid output file descriptor");
353        return 1;
354    }
355    TRACE("File descriptors: in %d out %d log %d", g_fdIn, g_fdOut, fileno(g_fLog));
356
357    /* This synchronizes the client with the server, so that the client
358     * doesn't start writing commands before the server is ready. It could
359     * also be used to supply information about the server (version, memory
360     * size, etc). */
361    fprintf(g_fOut, "GeoVis %s (build %s)\n", GEOVIS_VERSION_STRING, SVN_VERSION);
362    fflush(g_fOut);
363
364    g_renderer = new Renderer();
365    g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
366
367    g_renderer->setMaximumBitrate(maxbitrate);
368    if (resourcePath.empty()) {
369        resourcePath = "/usr/share/osgearth/data";
370    }
371    g_renderer->setResourcePath(resourcePath);
372    g_renderer->setIdleTimeout(idleTimeout);
373
374    TRACE("Default Tcl encoding dir: %s", Tcl_GetDefaultEncodingDir());
375    //Tcl_SetDefaultEncodingDir("encoding");
376
377    Tcl_Interp *interp = Tcl_CreateInterp();
378    initTcl(interp, NULL);
379
380#ifdef USE_THREADS
381    g_outQueue = new ResponseQueue();
382
383    pthread_t writerThreadId;
384    if (pthread_create(&writerThreadId, NULL, &writerThread, g_outQueue) < 0) {
385        ERROR("Can't create writer thread: %s", strerror(errno));
386    }
387#endif
388
389    osg::ref_ptr<osg::Image> imgData;
390
391    // Start main server loop
392    for (;;) {
393        struct timeval timeout;
394        g_renderer->getTimeout(&timeout);
395#ifdef SLEEP_AFTER_QUEUE_FRAME
396        g_renderer->markFrameStart();
397#endif
398        int cmdStatus = processCommands(interp, NULL, g_inBufPtr, g_fdOut, &timeout);
399        if (cmdStatus < 0)
400            break;
401
402        if (g_renderer->render()) {
403            FRAME("Rendered new frame");
404            imgData = g_renderer->getRenderedFrame();
405            if (imgData == NULL) {
406                ERROR("Empty image");
407            } else {
408                FRAME("Image: %d x %d", imgData->s(), imgData->t());
409
410                if (imgData->s() == g_renderer->getWindowWidth() &&
411                    imgData->t() == g_renderer->getWindowHeight()) {
412                    queueViewpoint();
413#ifdef USE_THREADS
414                    queueFrame(g_outQueue, imgData->data());
415#else
416                    writeFrame(g_fdOut, imgData->data());
417#endif
418                }
419                g_stats.nFrames++;
420                g_stats.nFrameBytes += imgData->s() * imgData->t() * 3;
421            }
422#ifdef SLEEP_AFTER_QUEUE_FRAME
423            g_renderer->markFrameEnd();
424#endif
425        } else {
426            //TRACE("No render required");
427            if (cmdStatus > 1) {
428                sendAck();
429            } else {
430                TRACE("No render required and status = %d", cmdStatus);
431            }
432        }
433
434#if 0
435        double x, y, z;
436        if (g_renderer->getMousePoint(&x, &y, &z)) {
437            // send coords to client
438            size_t length;
439            char mesg[256];
440
441            length = snprintf(mesg, sizeof(mesg),
442                              "nv>map coords %g %g %g\n", x, y, z);
443
444            queueResponse(mesg, length, Response::VOLATILE);
445        }
446#endif
447
448        if (g_inBufPtr->status() == ReadBuffer::ENDFILE)
449            break;
450    }
451#ifdef USE_THREADS
452    // Writer thread is probably blocked on sem_wait, so cancel instead
453    // of joining
454    if (pthread_cancel(writerThreadId) < 0) {
455        ERROR("Can't cancel writer thread: %s", strerror(errno));
456    } else {
457        TRACE("Cancelled writer thread");
458    }
459
460    TRACE("Deleting ResponseQueue");
461    delete g_outQueue;
462    g_outQueue = NULL;
463#endif
464
465    TRACE("Stopping Tcl interpreter");
466    exitTcl(interp);
467    interp = NULL;
468
469    TRACE("Deleting ReadBuffer");
470    delete g_inBufPtr;
471    g_inBufPtr = NULL;
472
473    std::string cacheDir = g_renderer->getCacheDirectory();
474    TRACE("Deleting renderer");
475    delete g_renderer;
476    g_renderer = NULL;
477    // Clean up cache directory after renderer's threads have finished
478    if (!cacheDir.empty()) {
479        removeDirectory(cacheDir.c_str());
480    }
481
482    TRACE("Exiting GeoVis Server");
483
484    closeLog();
485    exitService(0);
486
487    return 0;
488}
Note: See TracBrowser for help on using the repository browser.