PageRenderTime 66ms CodeModel.GetById 16ms app.highlight 44ms RepoModel.GetById 1ms app.codeStats 0ms

/src/SchemeProcess.cpp

http://github.com/digego/extempore
C++ | 550 lines | 482 code | 25 blank | 43 comment | 69 complexity | 67edb9f409d5112c48e6921020af3ba8 MD5 | raw file
  1/*
  2* Copyright (c) 2011, Andrew Sorensen
  3*
  4* All rights reserved.
  5*
  6*
  7* Redistribution and use in source and binary forms, with or without
  8* modification, are permitted provided that the following conditions are met:
  9*
 10* 1. Redistributions of source code must retain the above copyright notice,
 11*    this list of conditions and the following disclaimer.
 12*
 13* 2. Redistributions in binary form must reproduce the above copyright notice,
 14*    this list of conditions and the following disclaimer in the documentation
 15*    and/or other materials provided with the distribution.
 16*
 17* Neither the name of the authors nor other contributors may be used to endorse
 18* or promote products derived from this software without specific prior written
 19* permission.
 20*
 21*
 22* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 23* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 24* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 25* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 26* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 27* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 28* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 29* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 30* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 31* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 32* POSSIBILITY OF SUCH DAMAGE.
 33*
 34*/
 35
 36#include "TaskScheduler.h"
 37#include "SchemeProcess.h"
 38#include "SchemeFFI.h"
 39#include "OSC.h"
 40
 41#include <iosfwd>
 42#include <iomanip>
 43#include <stdexcept>
 44#include <errno.h>
 45
 46#include <sys/types.h>
 47#include <sys/stat.h>
 48
 49#ifdef _WIN32
 50#include <ws2tcpip.h>
 51
 52static void usleep(LONGLONG Us)
 53{
 54    auto timer(CreateWaitableTimer(NULL, TRUE, NULL));
 55    if (!timer) {
 56        return;
 57    }
 58    LARGE_INTEGER li;
 59    li.QuadPart = -Us * 10;
 60    if (!SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE)) {
 61        CloseHandle(timer);
 62        return;
 63    }
 64    WaitForSingleObject(timer, INFINITE);
 65    CloseHandle(timer);
 66}
 67#else
 68#include <sys/socket.h>
 69#include <sys/select.h>
 70#include <netinet/in.h>
 71#include <netinet/tcp.h>
 72#include <netdb.h>         /* host to IP resolution       */
 73#include <unistd.h>
 74static int closesocket(int Socket) {
 75    return close(Socket);
 76}
 77#endif
 78#include <stdlib.h>
 79#include "UNIV.h"
 80
 81#define EXT_INITEXPR_BUFLEN 1024
 82static const char TERMINATION_CHAR = 23;
 83
 84// FD_COPY IS BSD ONLY
 85#ifndef FD_COPY
 86#define FD_COPY(f, t) static_cast<void>(*(t) = *(f))
 87#endif
 88
 89namespace extemp {
 90namespace EXTLLVM {
 91
 92llvm_zone_t* llvm_zone_create(uint64_t);
 93
 94}
 95}
 96
 97#include "EXTLLVM.h"
 98
 99namespace extemp {
100
101THREAD_LOCAL SchemeProcess* SchemeProcess::sm_current = 0;
102const char* SchemeProcess::sm_banner = "\n"
103        "##########################################\n"
104        "##                                      ##\n"
105        "##               EXTEMPORE              ##\n"
106        "##                                      ##\n"
107        "##           andrew@moso.com.au         ##\n"
108        "##                                      ##\n"
109        "##            (c) 2005-2015             ##\n"
110        "##                                      ##\n"
111        "##########################################\n"
112        "     ################################\n"
113        "          ######################\n"
114        "               ############\n"
115        "                    ##\n\n";
116
117SchemeProcess::SchemeProcess(const std::string& LoadPath, const std::string& Name, int ServerPort, bool Banner,
118        const std::string& InitExpr): m_loadPath(LoadPath), m_name(Name), m_serverPort(ServerPort),
119        m_banner(Banner), m_initExpr(InitExpr), m_libsLoaded(false), m_guard("scheme_server_guard"),
120        m_running(true), m_threadTask(&taskTrampoline, this, "SP_task"),
121        m_threadServer(&serverTrampoline, this, "SP_server")
122{
123    if (m_loadPath[m_loadPath.length() - 1] != '/') {
124        m_loadPath.push_back('/');
125    }
126    m_scheme = scheme_init_new();
127    m_scheme->m_process = this;
128    m_defaultZone = extemp::EXTLLVM::llvm_zone_create(50 * 1024 * 1024); // allocate default zone of 50M
129    strcpy(m_scheme->name, m_name.c_str());
130    m_maxDuration = m_scheme->call_default_time;
131    memset(m_schemeOutportString, 0, SCHEME_OUTPORT_STRING_LENGTH);
132    scheme_set_output_port_string(m_scheme, m_schemeOutportString, m_schemeOutportString +
133            SCHEME_OUTPORT_STRING_LENGTH - 1);
134    FILE* initscm = fopen((m_loadPath + "runtime/init.xtm").c_str(), "r");
135    if (!initscm) {
136 	    ascii_error();
137		printf("ERROR:");
138		ascii_normal();
139        std::cout << " could not locate file init.xtm, exiting." << std::endl;
140        exit(1);
141    }
142    scheme_load_file(m_scheme, initscm);
143    m_serverSocket = socket(AF_INET, SOCK_STREAM, 0);
144    if (m_serverSocket < 0) {
145	    ascii_error();
146		printf("ERROR:");
147		ascii_normal();
148        std::cout << " could not open Extempore socket" << std::endl;
149        return;
150    }
151    int flag = 1;
152    setsockopt(m_serverSocket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&flag), sizeof(flag));
153    setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&flag), sizeof(flag));
154    scheme_define(m_scheme, m_scheme->global_env, mk_symbol(m_scheme, "*imp-envs*"), m_scheme->NIL);
155    scheme_define(m_scheme, m_scheme->global_env, mk_symbol(m_scheme, "*callback*"),
156            mk_cptr(m_scheme, mk_cb(this, SchemeProcess, schemeCallback)));
157    m_extemporeCallback = mk_cb(this, SchemeProcess, extemporeCallback);
158    SchemeFFI::initSchemeFFI(m_scheme);
159}
160
161bool SchemeProcess::start(bool subsume)
162{
163    //set socket options
164    int t_reuse = 1;
165    setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&t_reuse), sizeof(t_reuse));
166    struct sockaddr_in address;
167    memset(&address, 0, sizeof(address));
168    address.sin_family = AF_INET;
169    address.sin_port = htons(m_serverPort);
170    address.sin_addr.s_addr = htonl(INADDR_ANY); //set server's IP
171    if (bind(m_serverSocket, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0) {
172		ascii_error();
173		printf("ERROR:");
174		ascii_normal();
175        std::cout << " server: could not bind server socket on port " << m_serverPort << std::endl;
176        m_running = false;
177        return false;
178    }
179    if (listen(m_serverSocket, 5) < 0) {
180	    ascii_error();
181		printf("ERROR:");
182		ascii_normal();
183        std::cout << " problem listening to extempore socket" << std::endl;
184        m_running = false;
185        return false;
186    }
187    if (subsume) {
188      m_threadTask.setSubsume();
189    }
190    m_guard.init();    
191    m_threadServer.start();
192    m_threadTask.start();    
193    return true;
194}
195
196void SchemeProcess::stop()
197{
198    std::cout << "Stop Scheme Interface" << std::endl;
199    m_running = false;
200    scheme_deinit(m_scheme);
201    // TODO: what about sm_current?/name lookup
202}
203
204void SchemeProcess::addCallback(TaskI* TaskAdd, SchemeTask::Type Type)
205{
206    EXTMonitor::ScopedLock lock(m_guard, true);
207    auto currentTime(TaskAdd->getStartTime());
208    auto duration(TaskAdd->getDuration());
209    auto task(static_cast<Task<SchemeObj*>*>(TaskAdd));
210    m_taskQueue.push(SchemeTask(currentTime, duration, task->getArg(), "tmp_label", Type));
211}
212
213void SchemeProcess::createSchemeTask(void* Arg, const std::string& Label, SchemeTask::Type Type)
214{
215    EXTMonitor::ScopedLock lock(m_guard, true);
216    m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration, Arg, Label, Type));
217}
218
219bool SchemeProcess::loadFile(const std::string& File, const std::string& Path)
220{
221    auto fullPath((Path.empty() ? std::string() : (Path + "/")) + File);
222    auto impscm(fopen(fullPath.c_str(), "r"));
223    if (!impscm) {
224        std::cout << "ERROR: Unable to locate file: " << fullPath << std::endl;
225        return false;
226    }
227    scheme_load_file(m_scheme, impscm);
228    return true;
229}
230
231void* SchemeProcess::taskImpl()
232{
233    sm_current = this;
234    OSC::schemeInit(this);
235#ifdef _WIN32
236    Sleep(1000);
237#else
238    sleep(1); // give time for NSApp etc. to init
239#endif
240    while(!m_running) {
241    }
242    loadFile("runtime/scheme.xtm", UNIV::SHARE_DIR);
243    loadFile("runtime/llvmti.xtm", UNIV::SHARE_DIR);
244    loadFile("runtime/llvmir.xtm", UNIV::SHARE_DIR);
245    m_libsLoaded = true;
246#ifdef _WIN32
247    Sleep(2000);
248#else
249    sleep(2); // give time for NSApp etc. to init
250#endif
251    // only load extempore.xtm in primary process
252    if (m_name == "primary") {
253        EXTMonitor::ScopedLock lock(m_guard);
254        m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration,
255            new std::string("(sys:compile-init-ll)"), "file_init",
256                SchemeTask::Type::LOCAL_PROCESS_STRING));
257
258        if (extemp::UNIV::EXT_LOADBASE) {
259            m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration,
260                    new std::string("(sys:load \"libs/base/base.xtm\" 'quiet)"), "file_init",
261                        SchemeTask::Type::LOCAL_PROCESS_STRING));
262        } /* else {
263            m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration,
264                    new std::string("(sys:compile-init-ll)"), "file_init",
265                        SchemeTask::Type::LOCAL_PROCESS_STRING));
266        } */
267        if (!m_initExpr.empty()) {
268            ascii_text_color(0, 5, 10);
269            printf("\nEvaluating expression: ");
270            ascii_normal();
271            printf("%s\n\n", m_initExpr.c_str());
272            m_taskQueue.push(SchemeTask(extemp::UNIV::TIME + 1000, 60 * 60 * UNIV::SECOND(),
273                    new std::string(m_initExpr), "file_init", SchemeTask::Type::LOCAL_PROCESS_STRING));
274        }
275    }
276    while (likely(m_running)) {
277        if (unlikely(m_taskQueue.empty())) {
278            usleep(1000); // 1 ms
279            continue;
280        }
281        while (likely(!m_taskQueue.empty() && m_running)) {
282            m_guard.lock();
283            SchemeTask task = m_taskQueue.front();
284            m_taskQueue.pop();
285            m_guard.unlock();
286            switch (task.getType()) {
287            case SchemeTask::Type::DESTROY_ENV:
288                m_scheme->imp_env.erase(reinterpret_cast<pointer>(task.getPtr()));
289                break;
290            case SchemeTask::Type::LOCAL_PROCESS_STRING: //string from local process (MIDI, OSC or similar)
291                {
292                    auto evalString(reinterpret_cast<std::string*>(task.getPtr()));
293                    if (evalString->length() > 2) {
294                        uint64_t now(UNIV::TIME);
295                        scheme_load_string(m_scheme, evalString->c_str(), now, now + task.getMaxDuration());
296                        if (unlikely(m_scheme->retcode)) { //scheme error
297                            resetOutportString();
298                        }
299                    }
300                    delete evalString;
301                }
302                break;
303            case SchemeTask::Type::REPL:
304                {
305                    auto returnSocket(atoi(task.getLabel().c_str()));
306                    auto evalString(reinterpret_cast<std::string*>(task.getPtr()));
307                    if (evalString->length() > 1) {
308                        std::stringstream ss;
309                        bool write_reply(!!evalString->compare(0, 4, "(ipc"));
310                        if ((*evalString)[evalString->length() - 1] == TERMINATION_CHAR) {
311                            evalString->erase(--evalString->end());
312                        }
313                        uint64_t now(UNIV::TIME);
314                        scheme_load_string(m_scheme, (const char*) evalString->c_str(), now,
315                                now + task.getMaxDuration());
316                        if (unlikely(m_scheme->retcode)) { //scheme error
317                            resetOutportString();
318                        } else {
319                            if (m_banner) {
320                                auto time(UNIV::TIME);
321                                auto hours(time / UNIV::HOUR());
322                                time -= hours * UNIV::HOUR();
323                                auto minutes(time / UNIV::MINUTE());
324                                time -= minutes * UNIV::MINUTE();
325                                auto seconds(time / UNIV::SECOND());
326                                char prompt[24];
327                                sprintf(prompt, "\n[extempore %.2u:%.2u:%.2u]: ", unsigned(hours), unsigned(minutes), unsigned(seconds));
328                                ss << prompt;
329                            }
330                            UNIV::printSchemeCell(m_scheme, ss, m_scheme->value);
331                            if (write_reply) {
332                                auto res(ss.str());
333                                send(returnSocket, res.c_str(), int(res.length() + 1), 0);
334                            }
335                        }
336                    }
337                    delete evalString;
338                }
339                break;
340            case SchemeTask::Type::SCHEME_CALLBACK:
341                {
342                    auto obj(reinterpret_cast<SchemeObj*>(task.getPtr()));
343                    auto pair(reinterpret_cast<pointer>(obj->getValue()));
344                    auto func(pair_car(pair));
345                    auto args(pair_cdr(pair));
346                    if (is_closure(func) || is_macro(func) || is_continuation(func) || is_proc(func) ||
347                            is_foreign(func)) {
348                        uint64_t now(UNIV::TIME);
349                        scheme_call(m_scheme, func, args, now, now + task.getMaxDuration());
350                        if (unlikely(m_scheme->retcode)) { //scheme error
351                            resetOutportString();
352                        }
353                    } else {
354                        std::stringstream ss;
355                        UNIV::printSchemeCell(m_scheme, ss, pair);
356                        std::cerr << "Bad Closure ... " << ss.str() << " Ignoring callback request " << std::endl;
357                    }
358                    delete obj;
359                }
360                break;
361            case SchemeTask::Type::CALLBACK_SYMBOL: //callback with symbol as char*
362                {
363                    auto obj(reinterpret_cast<SchemeObj*>(task.getPtr()));
364                    auto symbol(reinterpret_cast<char*>(obj->getValue()));
365                    auto symbolsymbol(mk_symbol(m_scheme, symbol));
366                    auto func(pair_cdr(find_slot_in_env(m_scheme, m_scheme->global_env, symbolsymbol, 1)));
367                    pointer args = m_scheme->NIL;
368                    if (is_closure(func) || is_continuation(func) || is_proc(func) || is_foreign(func)) {
369                        uint64_t now(UNIV::TIME);
370                        scheme_call(m_scheme, func, args, now, now + task.getMaxDuration());
371                        if (m_scheme->retcode) { //scheme error
372                            resetOutportString();
373                        }
374                    } else {
375                        std::stringstream ss;
376                        extemp::UNIV::printSchemeCell(m_scheme, ss, func);
377                        std::cerr << "Bad Closure From Symbol ... " << ss.str() <<
378                                " Ignoring callback request " << std::endl;
379                    }
380                    delete obj;
381                }
382                break;
383            case SchemeTask::Type::EXTEMPORE_CALLBACK:
384                {
385                    auto s(reinterpret_cast<_llvm_callback_struct_*>(task.getPtr()));
386                    s->fptr(s->dat, s->zone);
387                }
388                break;
389            default:
390                std::cerr << "ERROR: BAD SchemeTask type!!" << std::endl;
391            }
392        }
393    }
394    std::cout << "Exiting task thread" << std::endl;
395    return this;
396}
397
398void* SchemeProcess::serverImpl()
399{
400    while (!m_libsLoaded) {
401        usleep(1000);
402    }
403    fd_set readFds;
404    std::vector<SOCKET> clientSockets;
405    std::map<SOCKET, std::string> inStrings;
406    FD_ZERO(&readFds); //zero out open sockets
407    FD_SET(m_serverSocket, &readFds); //add server socket to open sockets list
408    int numFds = int(m_serverSocket) + 1;
409    while (m_running) {
410        fd_set curReadFds;
411        FD_COPY(&readFds, &curReadFds);
412        int res(select(numFds, &curReadFds, NULL, NULL, nullptr));
413        if (unlikely(res < 0)) { // assumes only one failure
414            auto iter(clientSockets.begin());
415            for (; iter != clientSockets.end(); ++iter) {
416                struct stat buf;
417                if (fstat(int(*iter), &buf) < 0) {
418                    FD_CLR(*iter, &readFds);
419                    clientSockets.erase(iter);
420                    break;
421                }
422            }
423            ascii_error();
424            printf("%s SERVER ERROR: %s\n", m_name.c_str(), strerror(errno));
425            ascii_normal();
426            continue;
427        }
428        if (unlikely(FD_ISSET(m_serverSocket, &curReadFds))) { //check if we have any new accepts on our server socket
429            sockaddr_in client_address;
430            socklen_t clientAddressSize(sizeof(client_address));
431            auto res(accept(m_serverSocket, reinterpret_cast<sockaddr*>(&client_address), &clientAddressSize));
432            if (unlikely(res < 0)) {
433                std::cout << "Bad Accept in Server Socket Handling" << std::endl;
434                continue; //continue on error
435            }
436            numFds = int(res) + 1;
437            FD_SET(res, &readFds); //add new socket to the FD_SET
438			ascii_info();
439			printf("INFO:");
440			ascii_default();
441			std::cout << " server: accepted new connection to " << m_name << " process" << std::endl;
442            clientSockets.push_back(res);
443            inStrings[res].clear();
444            std::string outString;
445            if (m_banner) {
446                outString += sm_banner;
447                auto time(UNIV::TIME);
448                auto hours(time / UNIV::HOUR());
449                time -= hours * UNIV::HOUR();
450                auto minutes(time / UNIV::MINUTE());
451                time -= minutes * UNIV::MINUTE();
452                auto seconds(time / UNIV::SECOND());
453                char prompt[23];
454                sprintf(prompt, "[extempore %.2u:%.2u:%.2u]: ", unsigned(hours), unsigned(minutes), unsigned(seconds));
455                outString += prompt;
456            } else {
457                outString += "Welcome to extempore!";
458            }
459            send(res, outString.c_str(), int(outString.length() + 1), 0);
460            continue;
461        }
462        for (unsigned index = 0; index < clientSockets.size(); ++index) {
463            auto sock(clientSockets[index]);
464            const int BUFLEN = 1024;
465            char buf[BUFLEN + 1];
466            if (FD_ISSET(sock, &curReadFds)) { //see if any client sockets have data for us
467                std::string evalStr;
468                for (int j = 0; true; j++) { //read from stream in BUFLEN blocks
469                    res = recv(sock, buf, BUFLEN, 0);
470                    if (unlikely(!res)) { //close the socket
471                        FD_CLR(sock, &readFds);
472                        inStrings.erase(sock);
473                        ascii_info();
474						printf("INFO:");
475                        ascii_default();
476                        std::cout << " server: client disconnected" << std::endl;
477                        clientSockets.erase(clientSockets.begin() + index);
478                        closesocket(sock);
479                        --index;
480                        break;
481                    } else if (unlikely(res < 0)) {
482                        ascii_error();
483						printf("ERROR:");
484						ascii_default();
485						std::cout << " in socket read from extempore process " << strerror(errno) << std::endl;
486                        break;
487                    }
488                    auto& string(inStrings[sock]);
489                    buf[res] = '\0';
490                    string += buf;
491                    if (buf[res - 2] == 0x0d && buf[res - 1] == 0x0a) {
492                        evalStr.swap(string);
493                        break;
494                    }
495                    if (unlikely(j > 1024 *10)) {
496                        ascii_error();
497						printf("ERROR:");
498						ascii_default();
499                        std::cout << " eval string too large (no terminator received before 10MB limit)" << std::endl;
500                        ascii_normal();
501                        string.clear();
502                        break;
503                    }
504                }
505                if (likely(evalStr != "#break#")) {
506                    std::string::size_type pos = 0;
507                    std::string::size_type end = evalStr.find_first_of('\x0d', pos);
508                    for (; end != std::string::npos; pos = end + 2, end = evalStr.find_first_of('\x0d', pos)) {
509                        EXTMonitor::ScopedLock lock(m_guard, true);
510                        char c[8];
511                        sprintf(c, "%i", int(sock));
512                        std::string* s = new std::string(evalStr.substr(pos, end - pos + 1));
513                        // std::cout << extemp::UNIV::TIME << "> SCHEME TASK WITH SUBEXPR:" << *s << std::endl;
514                        m_taskQueue.push(SchemeTask(extemp::UNIV::TIME, m_maxDuration, s, c, SchemeTask::Type::REPL));
515                    }
516                }
517            }
518        }
519    }
520    for (auto sock : clientSockets) {
521        std::cout << "CLOSE CLIENT-SOCKET" << std::endl;
522        closesocket(sock);
523        std::cout << "DONE-CLOSING_CLIENT" << std::endl;
524    }
525    if (closesocket(m_serverSocket)) {
526        std::cerr << "SchemeProcess Error: Error closing server socket" << std::endl;
527        perror(NULL);
528    }
529    std::cout << "Exiting server thread" << std::endl;
530    return this;
531}
532
533SchemeObj::SchemeObj(scheme* Scheme, pointer Values, pointer Env): m_scheme(Scheme), m_values(Values), m_env(Env)
534{
535    if (unlikely(!Env)) {
536        std::cout << "BANG CRASH SHEBANG" << std::endl;
537        fflush(stdout);
538        abort();
539    }
540    m_scheme->imp_env.insert(Env);
541}
542
543SchemeObj::~SchemeObj()
544{
545    if (likely(m_env)) { // impossible to be null?
546        m_scheme->m_process->createSchemeTask(m_env, "destroy SchemeObj", SchemeTask::Type::DESTROY_ENV);
547    }
548}
549
550} // namespace imp