1
- from collections import defaultdict
2
- from ctypes import cdll , c_int , c_float , byref , POINTER
3
- from ctypes .util import find_library
4
- import os
5
- import platform
1
+ from ctypes import byref , c_float , c_int , POINTER
6
2
import random
7
- import re
8
- import shutil
9
- import subprocess
10
- import tempfile
11
- import uuid
12
3
import warnings
13
4
14
5
import axelrod as axl
15
6
from axelrod .interaction_utils import compute_final_score
16
7
from axelrod .action import Action
8
+
17
9
from .strategies import characteristics
10
+ from .shared_library_manager import MultiprocessManager , load_library
18
11
19
12
C , D = Action .C , Action .D
20
13
actions = {0 : C , 1 : D }
21
14
original_actions = {C : 0 , D : 1 }
22
15
23
16
24
- path_regex = r""".*?\s=>\s(.*?{}.*?)\\"""
25
-
26
17
self_interaction_message = """
27
18
You are playing a match with the same player against itself. However
28
19
axelrod_fortran players share memory. You can initialise another instance of an
29
20
Axelrod_fortran player with player.clone().
30
21
"""
31
22
32
23
33
- class LibraryManager (object ):
34
- """LibraryManager creates and loads copies of a shared library, which
35
- enables multiple copies of the same strategy to be run without the end user
36
- having to maintain many copies of the shared library.
37
-
38
- This works by making a copy of the shared library file and loading it into
39
- memory again. Loading the same file again will return a reference to the
40
- same memory addresses.
41
-
42
- Additionally, library manager tracks how many copies of the library have
43
- been loaded, and how many copies there are of each Player, so as to load
44
- only as many copies of the shared library as needed.
45
- """
46
-
47
- def __init__ (self , shared_library_name , verbose = False ):
48
- self .shared_library_name = shared_library_name
49
- self .verbose = verbose
50
- self .library_copies = []
51
- self .player_indices = defaultdict (set )
52
- self .player_next = defaultdict (set )
53
- # Generate a random prefix for tempfile generation
54
- self .prefix = str (uuid .uuid4 ())
55
- self .library_path = self .find_shared_library (shared_library_name )
56
- self .filenames = []
57
-
58
- def find_shared_library (self , shared_library_name ):
59
- # Hack for Linux since find_library doesn't return the full path.
60
- if 'Linux' in platform .system ():
61
- output = subprocess .check_output (["ldconfig" , "-p" ])
62
- for line in str (output ).split (r"\n" ):
63
- rhs = line .split (" => " )[- 1 ]
64
- if shared_library_name in rhs :
65
- return rhs
66
- raise ValueError ("{} not found" .format (shared_library_name ))
67
- else :
68
- return find_library (
69
- shared_library_name .replace ("lib" , "" ).replace (".so" , "" ))
70
-
71
- def load_dll_copy (self ):
72
- """Load a new copy of the shared library."""
73
- # Copy the library file to a new location so we can load the copy.
74
- temp_directory = tempfile .gettempdir ()
75
- copy_number = len (self .library_copies )
76
- new_filename = os .path .join (
77
- temp_directory ,
78
- "{}-{}-{}" .format (
79
- self .prefix ,
80
- str (copy_number ),
81
- self .shared_library_name )
82
- )
83
- if self .verbose :
84
- print ("Loading {}" .format (new_filename ))
85
- shutil .copy2 (self .library_path , new_filename )
86
- self .filenames .append (new_filename )
87
- shared_library = cdll .LoadLibrary (new_filename )
88
- self .library_copies .append (shared_library )
89
-
90
- def next_player_index (self , name ):
91
- """Determine the index of the next free shared library copy to
92
- allocate for the player. If none is available then load another copy."""
93
- # Is there a free index?
94
- if len (self .player_next [name ]) > 0 :
95
- return self .player_next [name ].pop ()
96
- # Do we need to load a new copy?
97
- player_count = len (self .player_indices [name ])
98
- if player_count == len (self .library_copies ):
99
- self .load_dll_copy ()
100
- return player_count
101
- # Find the first unused index
102
- for i in range (len (self .library_copies )):
103
- if i not in self .player_indices [name ]:
104
- return i
105
- raise ValueError ("We shouldn't be here." )
106
-
107
- def load_library_for_player (self , name ):
108
- """For a given player return a copy of the shared library for use
109
- in a Player class, along with an index for later releasing."""
110
- index = self .next_player_index (name )
111
- self .player_indices [name ].add (index )
112
- if self .verbose :
113
- print ("allocating {}" .format (index ))
114
- return index , self .library_copies [index ]
115
-
116
- def release (self , name , index ):
117
- """Release the copy of the library so that it can be re-allocated."""
118
- self .player_indices [name ].remove (index )
119
- if self .verbose :
120
- print ("releasing {}" .format (index ))
121
- self .player_next [name ].add (index )
122
-
123
- def __del__ (self ):
124
- """Cleanup temp files on object deletion."""
125
- for filename in self .filenames :
126
- if os .path .exists (filename ):
127
- os .remove (filename )
24
+ # Initialize a module-wide manager for loading copies of the shared library.
25
+ manager = MultiprocessManager ()
26
+ manager .start ()
27
+ shared_library_manager = manager .SharedLibraryManager ("libstrategies.so" )
128
28
129
29
130
30
class Player (axl .Player ):
131
31
132
32
classifier = {"stochastic" : True }
133
- library_manager = None
134
33
135
- def __init__ (self , original_name ,
136
- shared_library_name = 'libstrategies.so' ):
34
+ def __init__ (self , original_name ):
137
35
"""
138
36
Parameters
139
37
----------
@@ -144,17 +42,16 @@ def __init__(self, original_name,
144
42
A instance of an axelrod Game
145
43
"""
146
44
super ().__init__ ()
147
- if not Player .library_manager :
148
- Player .library_manager = LibraryManager (shared_library_name )
149
- self .index , self .shared_library = \
150
- self .library_manager .load_library_for_player (original_name )
45
+ self .index , self .shared_library_filename = \
46
+ shared_library_manager .get_filename_for_player (original_name )
47
+ self .shared_library = load_library (self .shared_library_filename )
151
48
self .original_name = original_name
152
49
self .original_function = self .original_name
153
-
154
50
is_stochastic = characteristics [self .original_name ]['stochastic' ]
155
51
if is_stochastic is not None :
156
52
self .classifier ['stochastic' ] = is_stochastic
157
53
54
+
158
55
def __enter__ (self ):
159
56
return self
160
57
@@ -216,15 +113,21 @@ def strategy(self, opponent):
216
113
my_last_move )
217
114
return actions [original_action ]
218
115
116
+ def _release_shared_library (self ):
117
+ try :
118
+ shared_library_manager .release (self .original_name , self .index )
119
+ except FileNotFoundError :
120
+ pass
121
+
219
122
def reset (self ):
220
- # Release the library before rest, which regenerates the player .
221
- self .library_manager . release ( self . original_name , self . index )
123
+ # Release the shared library since the object is rebuilt on reset .
124
+ self ._release_shared_library ( )
222
125
super ().reset ()
223
126
self .original_function = self .original_name
224
127
225
128
def __del__ (self ):
226
129
# Release the library before deletion.
227
- self .library_manager . release ( self . original_name , self . index )
130
+ self ._release_shared_library ( )
228
131
229
132
def __repr__ (self ):
230
133
return self .original_name
0 commit comments