-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathBluePython.cpp
More file actions
1968 lines (1620 loc) · 48.6 KB
/
Copy pathBluePython.cpp
File metadata and controls
1968 lines (1620 loc) · 48.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright © 2014 CCP ehf.
#include <sstream>
#include <string>
#include "StdAfx.h"
#if BLUE_WITH_PYTHON
#include "BluePython.h"
#include <BlueStatistics.h>
#include "IBlueOS.h"
#include "IMotherLode.h"
#include "IBlueResMan.h"
#include "IBlueObjectRecycler.h"
#include "IBluePaths.h"
#include <Scheduler.h>
#include "BlueLogInMemory.h"
#include "LogToPython.h"
#include "PrettyPrint.h"
#include "BlueHeapq.h"
#include "Marshal.h"
#include "PyRowSet.h"
#include "crypto.h"
#include "BlueClipboard.h"
#include "errormessage.h"
#include "PyMemory.h"
#if _WIN32
#include "win32.h"
#if CCP_STACKLESS
#include "Logger/Logger.h"
#endif
#endif
#if CCP_STACKLESS
#include "Synchro.h"
#endif
#if CCP_STACKLESS
#include "BlueNet.h"
#endif
#include "ScopedBlockTrap.h"
IBluePyOS* PyOS = nullptr;
static CcpLogChannel_t s_chPy = CCP_LOG_DEFINE_CHANNEL( "Python Logs" );
static CcpLogChannel_t s_chMemory = CCP_LOG_DEFINE_CHANNEL( "Memory" );
static const char* PORTNAMES[] =
{
"stdout",
"stderr",
"loginfo",
"logwarn",
"logerr",
"logfatal"
};
struct TimerString
{
const char* mName;
PyObject* mContext;
};
static TimerString TIMERS[] =
{
{"PyOS::Synchro Tick", NULL},
{"PyOS::PyError", NULL},
{"PyOS::Create Tasklet", NULL},
{"PyOS::Run Watchdog", NULL},
{"PyOS::StacklessIoDispatch", NULL},
};
// Timer strings
enum
{
TIMER_TICK,
TIMER_PYERROR,
TIMER_CREATETASKLET,
TIMER_RUNWATCHDOG,
TIMER_STACKLESSIO,
};
struct TaskletSwitch
{
const char* mName;
PyObject* mContext;
};
static TaskletSwitch TASKLETS[] =
{
{"Other tasklets", NULL},
{"Main tasklet", NULL},
{"Zombie tasklet", NULL},
};
static const int OTHERTASKLETS = 0;
static const int MAINTASKLET = 1;
static const int VOODOOTASKLET = 2;
CCP_STATS_DECLARE( runnablesLeftOver, "Blue/Synchro/runnablesLeftOver", false, CST_COUNTER_LOW, "Tasklets that were runnable but not scheduled" );
//use this because the TASKLETS thing may be cleared at the end.
class SafeAutoTasklet : public AutoTasklet
{
public:
SafeAutoTasklet(ITaskletTimer *timer, PyObject *context, bool active = true) :
AutoTasklet(timer, context, active && context != 0)
{}
};
//////////////////////////////////////////////////////////////////////
//
// BluePyOS class
//
//////////////////////////////////////////////////////////////////////
#if CCP_STACKLESS
// Tasklet performance counting
static int OnTaskletSwitch(PyTaskletObject *from, PyTaskletObject *to)
{
BlueStatistics::OnTaskletSwitch( (PyObject*)from, (PyObject*)to );
PyOS->OnTaskletSwitch( (PyObject*)from, (PyObject*)to );
return 0;
}
#endif
//--------------------------------------------------------------------
BluePyOS::BluePyOS(IRoot* lockobj) :
mSynchro( nullptr ),
mPySynchro( nullptr ),
mThreads( "BluePyOS/mThreads" ),
mScheduler(0.25), //initialize to min 4 fps
mMarkupZonesInPython(false),
mExceptionHandler( nullptr ),
PARENTLOCK( mCpuUsage ),
mSliceWarning( 200 ),
mBeNiceSlice( 15 ),
mPerformanceUpdateFrequency( 100000000 )
{
mBlueModule = 0;
mExitProcs = 0;
mContextHooks = 0;
mInit = 0;
mSoftspace = 0;
mPackaged = false;
mInterpreterMode = false;
//default value for optimize
mOptimizeFlag = -1;
//Context hooks:
mContextHooks = PyList_New(0);
//attribute strings
mstoredContext_str = BluePy(PyUnicode_InternFromString("storedContext"));
mrunTime_str = BluePy(PyUnicode_InternFromString("runTime"));
}
PyTypeObject *LogChannelType();
// This is a little Python helper script to allow loading flavoured Python C extensions through a simple `import extension_without_flavor`.
// It kind of obsoletes the old `blue.LoadExtension` helper method.
const char* IMPORT_HOOK_HELPER_SCRIPT = R"(
import importlib.machinery
import os
import sys
class BlueFlavoredExtensionFileLoader(importlib.machinery.ExtensionFileLoader):
def __init__(self, name, path, flavor):
super().__init__(name, path)
self.flavor = flavor
def create_module(self, spec):
import sys
unflavored_name = spec.name
spec.name = f"{spec.name}{self.flavor}"
module = super().create_module(spec)
sys.modules[unflavored_name] = module
sys.modules[spec.name] = module
return module
class BlueFlavoredExtensionImporter:
def __init__(self, flavor, binary_path, loader_class):
import _imp
import os
self.flavor = flavor
self.loader_class = loader_class
self.bin_path = os.path.dirname(binary_path) if os.path.isfile(binary_path) else binary_path
self.extension_suffixes = [f"{self.flavor}{suffix}" for suffix in _imp.extension_suffixes()]
def find_spec(self, fullname, path=None, target=None):
import importlib
import os
import sys
for suffix in self.extension_suffixes:
search_paths = [os.path.join(search_path, f"{fullname}{suffix}") for search_path in sys.path + [self.bin_path]]
found = False
for search_path in search_paths:
if os.path.exists(search_path):
found = True
origin = search_path
if found:
break
else:
return None # no flavored version was found, return None
return importlib.machinery.ModuleSpec(fullname, self.loader_class(fullname, origin, self.flavor), origin=origin)
sys.meta_path.insert(0, BlueFlavoredExtensionImporter(CCP_BUILD_FLAVOR, CCP_EXEFILE_PATH, BlueFlavoredExtensionFileLoader))
)";
bool InstallImportHook()
{
// Extend the Pyhon import mechanism with support for C extensions that use our "build flavors".
// This needs to happen before we import any flavored C extensions.
#if ~(~CCP_BUILD_FLAVOR + 0) == 0 && ~(~CCP_BUILD_FLAVOR + 1) == 1
// this is not a flavoured build, so no import hook is needed
return true;
#else
PyObject* globalDict = PyDict_New();
if ( !globalDict ) {
CCP_LOGERR_CH( s_chPy, "Failed to create global dict for import helper script execution");
return false;
}
auto executablePath = WideToUTF8( CcpExecutablePath() );
PyObject* localDict = Py_BuildValue( "{ssss}", "CCP_BUILD_FLAVOR", CCP_STRINGIZE( CCP_BUILD_FLAVOR ), "CCP_EXEFILE_PATH", executablePath.c_str() );
if ( !localDict ) {
CCP_LOGERR_CH( s_chPy, "Failed to create local dict for import helper script execution");
Py_DecRef( globalDict );
return false;
}
if ( ! PyRun_String( IMPORT_HOOK_HELPER_SCRIPT, Py_file_input, globalDict, localDict ) )
{
CCP_LOGERR_CH( s_chPy, "Failed executing import helper script" );
Py_DecRef( localDict );
Py_DecRef( globalDict );
return false;
}
Py_DecRef( localDict );
Py_DecRef( globalDict );
return true;
#endif
}
//--------------------------------------------------------------------
bool BluePyOS::InitBasicModuleSupport()
{
// put myself into python as a module
static struct PyModuleDef moduleDef {
PyModuleDef_HEAD_INIT,
"blue",
"",
-1,
nullptr,
};
mBlueModule = PyModule_Create( &moduleDef );
PyObject* dict = PyModule_GetDict(mBlueModule); //borrowed ref
BlueRegisterToModule( mBlueModule, BlueRegistration::GetClassRegs(),
BlueRegistration::GetFuncRegs(),
BlueRegistration::GetEnumRegs(),
BlueRegistration::GetTestRegs(),
BlueRegistration::GetThunkerRegs(),
BlueRegistration::GetFuncSignatures() );
BlueRegisterObjectsToModule( mBlueModule, BlueRegistration::GetObjectRegs() );
BlueRegisterExceptionsToModule( mBlueModule, BlueRegistration::GetExceptionRegs() );
// Get ref to schedule manager for main thread
// This will ensure the main thread's schedule manager alive throughout game usage
mMainScheduler = BluePy( SchedulerAPI()->PyScheduler_GetScheduler() );
// Set the main tasklet as block trapped.
BluePy currentTasklet(SchedulerAPI()->PyScheduler_GetCurrent());
if (!currentTasklet)
return false;
SchedulerAPI()->PyTasklet_SetBlockTrap((PyTaskletObject*)(PyObject*)currentTasklet, 1);
// Initialize error exception
PyDict_SetItemString(dict, "error", PyExc_BlueError);
PyModule_AddObject( mBlueModule, "logInMemory", BlueWrapObjectForPython( BlueLogInMemory::GetInstance() ));
// Insert PyOS as python object
if (PyModule_AddObject(mBlueModule, "pyos", BlueWrapObjectForPython(PyOS)))
return false;
// Add the blue wrapper
if (PyModule_AddObject(mBlueModule, "BlueWrapper", (PyObject*)BePyTypePtr))
return false;
// Add the logchannel object
if (PyDict_SetItemString(dict, "LogChannel", (PyObject*)LogChannelType()))
return false;
PyObject* heapqModule = InitHeapq();
if ( !heapqModule )
throw PyError();
if ( PyModule_AddObject( mBlueModule, "heapq", heapqModule ) ) {
return false;
}
// Add the DBRowsetStuff
if( !DBRowsetInit( mBlueModule ) )
return false;
#if _WIN32
//init the submodules blue.win32 and blue.heapq. The latter is required for the Marshal::New()
if ( !initwin32(mBlueModule) )
return false;
#endif
if( !InitCryptoModule(mBlueModule) )
return false;
if (!MarshalInit(mBlueModule))
return false;
// Insert custom marshaller
PyObject* marshal = Marshal::New();
if( marshal )
{
if( PyModule_AddObject( mBlueModule, "marshal", marshal ) )
return false;
}
PyObject* sys_modules = PyImport_GetModuleDict();
#if CCP_STACKLESS
auto carbonSocketModule = PyImport_ImportModule( "_carbonsocket" );
if( !carbonSocketModule )
{
CCP_LOGERR_CH( s_chPy, "Failed to import `_carbonsocket` module" );
return false;
}
if( PyDict_SetItemString( sys_modules, "_socket", carbonSocketModule ) != 0 )
{
CCP_LOGERR_CH( s_chPy, "Failed to patch `_socket` module" );
return false;
}
// Reload socket module with patched _socket module in order for the module
// to pick up on our _socket implementation. This is necessary if the socket module
// has already been imported.
if( auto socketModule = PyDict_GetItemString( sys_modules, "socket" ) )
{
PyImport_ReloadModule( socketModule );
}
auto carbonSSLModule = PyImport_ImportModule( "_carbonssl" );
if( !carbonSSLModule )
{
CCP_LOGERR_CH( s_chPy, "Failed to import `_carbonssl` module" );
return false;
}
if( PyDict_SetItemString( sys_modules, "_ssl", carbonSSLModule ) != 0 )
{
CCP_LOGERR_CH( s_chPy, "Failed to patch `_ssl` module" );
return false;
}
auto carbonSelectModule = PyImport_ImportModule( "carbonselect" );
if( !carbonSelectModule )
{
CCP_LOGERR_CH( s_chPy, "Failed to import `carbonselect` module" );
return false;
}
if( PyDict_SetItemString( sys_modules, "select", carbonSelectModule ) != 0 )
{
CCP_LOGERR_CH( s_chPy, "Failed to patch select module" );
return false;
}
// c-routing support
if ( !BeNet->Init( mBlueModule, mSocketAPI ) ) {
return false;
}
mSynchro = CCP_NEW( "BluePyOS/mSyncro" ) Synchro;
mPySynchro = mSynchro;
PyDict_SetItemString(dict, "synchro", mSynchro);
#endif
if ( PyDict_SetItemString( sys_modules, "blue.heapq", heapqModule ) != 0 ) {
return false;
}
return true;
}
//--------------------------------------------------------------------
bool BluePyOS::FiniBasicModuleSupport()
{
#if CCP_STACKLESS
//synchro and pysynchro share a reference
//Todo, this should go somewhere else
mPySynchro = 0;
Synchro *synchro = mSynchro;
mSynchro = 0;
synchro->Shutdown();
Py_DECREF(synchro);
#endif
//Get the Blue module dict
PyObject* dict = PyModule_GetDict(mBlueModule); //borrowed reference
// clear the module dict
PyDict_Clear(dict);
Py_DECREF(PyExc_BlueError);
PyExc_BlueError = 0;
//now, get the sys.modules dict
dict = PyImport_GetModuleDict();
PyObject *modules = PyDict_GetItemString(dict, "modules");
//and delete the "blue" module (by replacing it with None)
PyDict_SetItemString(modules, g_moduleName, Py_None);
mBlueModule = 0;
return true;
}
//--------------------------------------------------------------------
BluePythonObject* BluePyOS::WrapBlueObject(
IRoot* object
)
{
BluePythonObject* ret = BlueWrapObjectForPython(object);
return ret;
}
//--------------------------------------------------------------------
#if CCP_STACKLESS
//--------------------------------------------------------------------
static PyObject* ClockThisImpl(PyObject* ctx, PyObject* fn, PyObject* args, PyObject* kw)
{
//Be a bit careful here, to pass on any error generatoed by the actual call.
PyObject *result, *etype, *evalue, *etb;
{
AutoTasklet _at(PyOS->GetTaskletTimer(), ctx);
result = PyObject_Call(fn, args, kw);
PyErr_Fetch(&etype, &evalue, &etb);
}
PyErr_Restore(etype, evalue, etb);
return result;
}
//--------------------------------------------------------------------
static PyObject* ClockThis(PyObject*, PyObject* args, PyObject* kw)
{
// def ClockThis(ctx, fn, *args, **kw)
if (PyTuple_GET_SIZE(args) >= 2)
{
PyObject* ctx = PyTuple_GET_ITEM(args, 0);
PyObject* fn = PyTuple_GET_ITEM(args, 1);
if( PyUnicode_Check(ctx) && PyCallable_Check(fn) )
{
PyObject* realArgs = PyTuple_GetSlice(args, 2, PyTuple_GET_SIZE(args));
PyObject* ret = ClockThisImpl(ctx, fn, realArgs, kw);
Py_DECREF(realArgs);
return ret;
}
}
PyErr_SetString(PyExc_TypeError, "I want string/unicode as first argument and callable as second");
return 0;
}
//--------------------------------------------------------------------
static PyObject* ClockThisWithoutStars(PyObject*, PyObject* args)
{
// def ClockThisWithoutTheStars(ctx, fn, args, kw={})
PyObject* ctx;
PyObject* fn;
PyObject* realArgs;
PyObject* kw = NULL;
if (!PyArg_ParseTuple(args, "OOO!|O!", &ctx, &fn, &PyTuple_Type, &realArgs, &PyDict_Type, &kw))
return NULL;
if (!PyUnicode_Check(ctx))
return PyErr_SetString(PyExc_TypeError, "I want a string context as first argument"), nullptr;
if (!PyCallable_Check(fn))
{
PyErr_SetString(PyExc_TypeError, "I want a callable as second argument");
return NULL;
}
return ClockThisImpl(ctx, fn, realArgs, kw);
}
CCP_STATS_DECLARED_ELSEWHERE( pyMemory );
static PyObject* PyGetPyMalloced(PyObject*, PyObject*)
{
return PyLong_FromLongLong( int64_t( CCP_STATS_GET( pyMemory ) ) );
}
//--------------------------------------------------------------------
// ClockThis utility functions
static PyMethodDef clockthis[] = {
{"ClockThis", (PyCFunction)ClockThis, METH_VARARGS | METH_KEYWORDS, "ClockThis utility"},
{"ClockThisWithoutTheStars", ClockThisWithoutStars, METH_VARARGS, "ClockThisWithoutTheStars utility"},
// `getpymalloced` is not related to clockthis at all, but you know, it's also a customization to `sys`
{"getpymalloced", PyGetPyMalloced, METH_VARARGS, "Returns the size of memory allocated by Python, in bytes."},
{nullptr}
};
#endif
void BluePyOS::EnsureAssertionsEnabled()
{
#ifdef _MSC_VER
//Python turns off assertions for the C runtime! Undo that
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_WNDW);
#endif
}
bool BluePyOS::Startup()
{
if (mInit)
return true;
BeTimer timer("BluePyOS::Startup()");
//simple tasklet timing
mLastSwitchTime = CcpGetTimestamp();
mSwitchTimePeriod = 1.0/double( CcpGetTimestampFrequency() );
mLastThreadCpuUsage = mLastProcessCpuUsage = 0;
mLastThreadKernelUsage = mLastProcessKernelUsage = 0;
mLastCpuUpdate = 0;
mInterpreterMode = BeOS->HasStartupArg( L"py" );
this->EnsureAssertionsEnabled();
for (unsigned int i = 0; i < sizeof TIMERS / sizeof *TIMERS; i++)
TIMERS[i].mContext = PyUnicode_InternFromString(TIMERS[i].mName);
// insert clockthis utility functions
for (PyMethodDef* md = clockthis; md->ml_meth != NULL; md++)
{
PyObject* fn = PyCFunction_New(md, NULL);
if ( PySys_SetObject((char*)md->ml_name, fn) == -1 ) {
CCP_LOGERR( "Failed adding %s to sys module", md->ml_name );
}
Py_DECREF(fn);
}
for (unsigned int i = 0; i < sizeof TASKLETS / sizeof TASKLETS[0]; i++)
{
TASKLETS[i].mContext = PyUnicode_InternFromString(TASKLETS[i].mName);
}
if ( !InstallImportHook() )
{
return false;
}
mSocketAPI = reinterpret_cast<PySocketModule_APIObject*>( PySocketModule_ImportModuleAndAPI() );
if ( !mSocketAPI )
{
CCP_LOGERR( "Failed acquiring carbon-io socket module" );
PyFlushError( nullptr );
return false;
}
CCP_LOG( "Init basic module support" );
if (!InitBasicModuleSupport())
{
CCP_LOGERR( "InitBasicModuleSupport() failed" );
PyFlushError(0);
Py_Finalize();
return false;
}
if ( !mTTimer.InitPythonObjects() ) {
CCP_LOGERR( "Failed initalizing TaskletTimers" );
PyFlushError( nullptr );
return false;
}
// Reset the tasklet counter (can only do this after pyinit and module support, we register modules and types)
mTTimer.Reset();
SchedulerAPI()->PyScheduler_SetScheduleFastCallback( ::OnTaskletSwitch );
mInit = true;
return true;
}
//--------------------------------------------------------------------
void BluePyOS::Shutdown(int level)
{
// If Telemetry was running we have to stop it before Python goes
// down as it assumes tasklets are active.
g_statistics->StopTelemetry();
g_statistics->Update();
if (!mInit)
return ;
if (level == 1) {
//shutdown, level 1. Benign exit, call exit handlers, kill tasklets, etc.
// Make sure we release a potential reflock to an external Python object:
SetLogEchoFunction( CCP::LOGTYPE_LOWEST, Py_None );
// call exit procs
BluePy bluepy(PyImport_ImportModule("bluepycore"));
if (bluepy) {
CCP_LOG_CH( s_chPy, "Running bluepycore.Shutdown");
BluePy res(BluePy(PyObject_CallMethod(bluepy, const_cast<char*>( "Shutdown" ), const_cast<char*>( "O" ), mExitProcs?mExitProcs:Py_None)));
Py_CLEAR(mExitProcs);
if (!res)
HandleException("BluePyOS::Shutdown::bluepycore.Shutdown");
CCP_LOG_CH( s_chPy, "Finished running bluepycore.Shutdown");
}
PyErr_Clear();
return;
}
//Delete all deco objects. They hold tons of references. Shouldn't be necessary,
//but it's prophylactic.
// todo: BlueWrapper::ReleaseAllDecos();
// Detach app exception handler
Py_XDECREF(mExceptionHandler);
mExceptionHandler = NULL;
// Finish basic module support
FiniBasicModuleSupport();
#if CCP_STACKLESS
SchedulerAPI()->PyScheduler_SetScheduleFastCallback( 0 );
Py_XDECREF(mContextHooks);
mrunTime_str.Release();
mstoredContext_str.Release();
ssize_t i;
for (i = 0; i < sizeof TASKLETS / sizeof TASKLETS[0]; i++) {
Py_DECREF(TASKLETS[i].mContext);
TASKLETS[i].mContext = 0;
}
// remove clockthis utility functions
for (PyMethodDef* md = clockthis; md->ml_meth != NULL; md++)
PySys_SetObject((char*)md->ml_name, 0);
// Release timer names
for (i = 0; i < sizeof TIMERS / sizeof *TIMERS; i++) {
Py_DECREF(TIMERS[i].mContext);
TIMERS[i].mContext = 0;
}
#endif
// Shut down python now!
Py_Finalize();
// Now, all python references to the blue wrappers should be gone.
// todo: BlueWrapper::Shutdown();
mInit = false;
}
#if CCP_STACKLESS
//Return a borrowed reference to the stored context, or the creation context.
static PyObject *GetContext(PyTaskletObject *task)
{
if (!task)
return TASKLETS[VOODOOTASKLET].mContext;
if(SchedulerAPI()->PyTasklet_IsMain(task))
return TASKLETS[MAINTASKLET].mContext;
PyObject *r = PyObject_GetAttrString((PyObject*)task, "storedContext");
Py_XDECREF(r);
if (!r || r == Py_None) {
PyObject *rr = PyObject_GetAttrString((PyObject*)task, "context");
Py_XDECREF(rr);
if (rr)
r = rr;
else
PyErr_Clear();
}
if (r)
return r;
PyErr_Clear();
return TASKLETS[OTHERTASKLETS].mContext;
}
#endif
//--------------------------------------------------------------------
void BluePyOS::OnTaskletSwitch(PyObject* _from, PyObject* _to)
{
#if CCP_STACKLESS
//new api. NULL -> non-zero: entering main tasklet
// non-null -> non-zero: switching between tasklets, possibly on tasklet exit,
// possibly on tasklet entry.
CCP_ASSERT((!_from && _to) || (_from && _to));
PyTaskletObject *from = (PyTaskletObject*)_from, *to = (PyTaskletObject*)_to;
//time for simple tasklet timing;
double elapsed = GetTimeSinceSwitch(true);
//We need to preserve any error state:
PyObject *etype, *evalue, *etraceback;
PyErr_Fetch(&etype, &evalue, &etraceback);
PyObject *prev = mTTimer.SwitchStack((intptr_t)to);
//Store the context we came from in our tasklet extension object.
bool fromNotMain = from && !SchedulerAPI()->PyTasklet_IsMain(from);
if (fromNotMain && PyObject_SetAttr((PyObject*)from, mstoredContext_str, prev) < 0)
PyErr_Clear();
Py_DECREF(prev);
//update tasklet run time
if (fromNotMain) {
if (!UpdateTaskletRunTime((PyObject*)from, elapsed))
PyErr_Clear();
}
//Call the other context hooks
if (!from)
from = (PyTaskletObject*)Py_None;
if (!to)
to = (PyTaskletObject*)Py_None;
Py_ssize_t n = PyList_GET_SIZE(mContextHooks);
for (Py_ssize_t i=0; i<n; i++) {
PyObject *hook = PyList_GET_ITEM(mContextHooks, i);
PyObject *result = PyObject_CallFunction(hook, (char*)"OO", from, to);
Py_XDECREF(result);
if (!result)
PyError();
}
//restore previous error state
PyErr_Restore(etype, evalue, etraceback);
#endif
}
#if CCP_STACKLESS
//--------------------------------------------------------------------
double BluePyOS::GetTimeSinceSwitch(bool update)
{
auto now = CcpGetTimestamp();
double elapsed = (double(now)-double(mLastSwitchTime)) * mSwitchTimePeriod;
if (update)
mLastSwitchTime = now;
return elapsed;
}
//--------------------------------------------------------------------
bool BluePyOS::UpdateTaskletRunTime(PyObject *tasklet, double elapsed)
{
BluePy oldObj(PyObject_GetAttr((PyObject*)tasklet, mrunTime_str));
if (!oldObj)
return false; //it has to be there already.
double oldVal = PyFloat_AS_DOUBLE(oldObj.o); //ignore errors here.
BluePy newObj(PyFloat_FromDouble(oldVal + elapsed));
if (!newObj)
return false;
if (PyObject_SetAttr((PyObject*)tasklet, mrunTime_str, newObj) < 0)
return false;
return true;
}
#endif
//--------------------------------------------------------------------
int BluePyOS::PumpPython(bool quit)
{
#if CCP_STACKLESS
if (PyErr_Occurred())
PyFlushError("PumpPython::start");
{
CCP_STATS_ZONE( "Blue/CarbonIO" );
SafeAutoTasklet _at(&mTTimer, TIMERS[TIMER_STACKLESSIO].mContext);
mSocketAPI->dispatch();
}
// Synchro. This will make tasklets runnable
{
CCP_STATS_ZONE( "Blue/synchro" );
SafeAutoTasklet _at(&mTTimer, TIMERS[TIMER_TICK].mContext);
if (!quit && !mSynchro->Tick())
PyFlushError("PumpPython::synchro");
}
// Run from the runnable queue
{
CCP_STATS_ZONE( "Blue/RunWatchdog" );
SafeAutoTasklet _at(&mTTimer, TIMERS[TIMER_RUNWATCHDOG].mContext);
if( !mScheduler.Run() )
{
PyFlushError("PumpPython::Scheduler");
}
CCP_STATS_SET( runnablesLeftOver, SchedulerAPI()->PyScheduler_GetRunCount() - 1 );
}
LogCpuUsageAndOtherStats();
#endif
return 1;
}
//custom formatting of traceback for those early startup problems
static std::string FormatTraceback(PyObject *tb)
{
std::string result = "Traceback (most recent call first):\n";
BluePy t(tb, true);
while (t && PyObject_IsTrue(t) ){
std::string filename = "<none>";
BluePy frame(PyObject_GetAttrString(t, "tb_frame"));
if (frame) {
BluePy code(PyObject_GetAttrString(frame, "f_code"));
if (code) {
BluePy fname(PyObject_GetAttrString(code, "co_filename"));
if (fname)
filename = std::string(PyUnicode_AsUTF8(fname));
}
}
PyErr_Clear();
int line = 0;
BluePy lineno(PyObject_GetAttrString(t, "tb_lineno"));
if (lineno)
line = int( PyLong_AsLong(lineno) );
PyErr_Clear();
char clineno[16];
sprintf_s(clineno, "%i\n", line);
result += filename+":"+clineno;
t = BluePy(PyObject_GetAttrString(t, "tb_next"));
}
result += "Traceback end\n";
PyErr_Clear();
return result;
}
//This we may need if we get an exception very early, when 'traceback' isn't avaliable.
static std::string FormatExceptionFallback(PyObject *type, PyObject *val, PyObject *tb)
{
std::string result = "Simple exception format:\n";
BluePy s(PyObject_Repr(type));
if (s)
result += std::string("Type: ") + PyUnicode_AsUTF8(s) + "\n";
if (val) {
s = BluePy(PyObject_Repr(val));
if (s)
result += std::string("Value: ") + PyUnicode_AsUTF8(s) + "\n";
}
if (tb)
result += FormatTraceback(tb);
result += "Simple exception end\n";
PyErr_Clear();
return result;
}
void BluePyOS::FormatException(char **result)
{
*result = 0;
if (!PyErr_Occurred()) {
//there was no exception!
*result = CCP_STRDUP( "FormatException", "FormatException() called with no pending python exception" );
return;
}
PyObject *type, *val, *tb;
PyErr_Fetch(&type, &val, &tb);
{
//import the traceback module
BluePy module(PyImport_ImportModule("traceback"));
if (!module) goto ERR;
BluePy lines(PyObject_CallMethod(module, const_cast<char*>( "format_exception" ), const_cast<char*>( "OOO" ), type, val?val:Py_None, tb?tb:Py_None));
if (!lines) goto ERR;
BluePy str(PyUnicode_FromString(""));
if (!str) goto ERR;
str = BluePy(PyObject_CallMethod(str, const_cast<char*>( "join" ), const_cast<char*>( "O" ), lines.o));
if (!str) goto ERR;
*result = CCP_STRDUP( "FormatException", PyUnicode_AsUTF8(str) );
PyErr_Restore(type, val, tb);
return;
}
ERR:
// Silly failure. Can happen if we have not initialized traceback module yet.
PyErr_Clear();
*result = CCP_STRDUP( "FormatException", FormatExceptionFallback(type, val, tb).c_str() );
PyErr_Restore(type, val, tb);
//also print it and clear it in this error case
PyErr_Print();
PyErr_Clear();
}
//--------------------------------------------------------------------
// BluePyOS::PyError. This is used to turn python exceptions into blue errors
//--------------------------------------------------------------------
PyObject* BluePyOS::PyError(
PyObject* exception
)
{
if (exception)
return exception; //this is not an exception!
SafeAutoTasklet _at(GetTaskletTimer(), TIMERS[TIMER_PYERROR].mContext);
if (!PyErr_Occurred()) {
BeOS->SetError(BEDEF, Clsid(), "Trying to report a python exception with none pending.");
return 0;
}
HandleException("BluePyOS::PyError()");
return 0;
}
//--------------------------------------------------------------------
// BluePyOS::PyFlushError. This is used to turn python exceptions into blue errors
// Similar to PyError, but accepts a "whence" message, and is okay for there to
// be no error. Use this to periodically check for leaking errors, or to report
// known errors with a proper "whence" message. Returns true if an error was found
//--------------------------------------------------------------------
bool BluePyOS::PyFlushError(const char *message)
{
if (!PyErr_Occurred())
return false;
AutoTasklet _at(GetTaskletTimer(), "PyOS::PyFlushError");
HandleException(message);
return true;
}
// Actual handling of generating the exception
void BluePyOS::HandleException(const char *message)
{
PyObject* type;
PyObject* value;
PyObject* traceback;
PyErr_Fetch(&type, &value, &traceback);
CCP_ASSERT(type);
PyErr_NormalizeException(&type, &value, &traceback);
if (!message)
message = "";
bool handled = false;
bool failed = false;
if (mExceptionHandler) {
// Avoid recursion if exception handler is screwey
PyObject* exhandler = mExceptionHandler;
mExceptionHandler = NULL;
// later we can add some info here, extra stuff, to give the handler context.
// value and traceback can be Null
failed = true;