Use Strict String Key Map

일반적으로 Parameter 전달은 가끔씩 고민을 하게 만드는 문제입니다.

parameter[String key] = value 방식은 간단하고 다수의 파라패터를 쉽게 넘길 수 있습니다.

그러나 String 의 특성상, 오타를 내면 바로 바로 오동작을 일으키게 만드는 원인이 될 수 있습니다.

그런데, 조금만 생각을 추가하면, 넣을 수 있는 ‘Key’를 제한 한다면 어떨까요?

아주 간단하게 Key Name 을 제한해서 이미 허용해둔 값들만 Set 할 수 있도록 만들면 간단하지 않을까요?

물론, 허용 Key Name을 추가할 수 있도록 만들어 두고, 테스트 코드에서 한번씩 확인하게 해주면,

의외로 간단하면서, 실수하기 쉬운 파라매터나 Conf 설정을 쉽게 할 수 있을듯 합니다.

다음은 위에 말한 기법을 이용한 python singleton 예제입니다.

 class Parameter(object):
    key_list = [ 'name', 'price', 'size' ]

    def init(self):
        self.m_param_keyword = {}
        self.m_param_dict = {}

        for key in self.key_list:
            self.add_allow_key( key )

    def get_keys(self):
        return self.m_param_dict.keys()

    def get_allow_keys(self):
        return self.m_param_keyword.keys()

    def add_allow_key(self, key):
        self.m_param_keyword[ key.lower() ] = True

    def add_allow_keys( self, keys ):
        for key in keys:
            self.add_allow_key( key )

    def is_valid_key( self, key ):
        if self.m_param_keyword.get(key, False) == True:
            return True

        return False

    def __init__(self):
        self.init()

    def set( self, key, value):
        lower_key = key.lower()
        if self.is_valid_key( lower_key ) == False:
            return False

        self.m_param_dict[lower_key] = value
        return True

    def get( self, key ):
        return self.m_param_dic[key]

if __name__ == '__main__':
    paramter = Parameter()

    print parameter.set( 'name', 'charsyam' )
    print parameter.set( 'price2', 10 )

    parameter.add_allow_keys( ['inventory'] )
    print parameter.get_allow_keys()

    print id(conf1), id(conf2)

File Description Passing with Python

요새 Python 에 대해서 공부를 하고 있습니다. 정확하게는 Python 을 이용한 서버인데

서버의 대세는 Multi-Core, Multi-Threading 입니다. 그런데

cPython 의 경우에 GIL(Global Interpreter Lock) 이라는 무서운 녀석이 이 Multi-Threading 을

막고 있습니다.( JPython 이나 IronPython 의 경우는 Lock 구현이 달라서 이런 문제가 없다고 합니다.

그러나 많은 모듈들이 cPython 으로 존재하고 있어서 T.T )

GIL은 다음 사이트에서 http://www.dabeaz.com/python/UnderstandingGIL.pdf 자세한 정보를 얻을 수

있습니다.

그래서 cPython 을 쓸 때 Multi-Core 를 활용하기 좋은 모델은 Multi-Thread 가 아니라 Multi-Process

라고 할 수 있습니다. 그렇다면 Multi-Process를 활용한 서버 모델에는 어떤 것들이 있을까요?

일반적으로 서버 프로그래밍을 조금 공부하셨던 분들은(절대로 하셨던 분들이 아니라 공부하셨던 분입니다. -__- )

Preforked 방식이나, Process Per Client 방식에 대해서 아실껍니다.

일반적으로 적은 규모에서는 Client Per Process 의 방식이 구현이 편하고, 적당한 성능을 발휘해 주는 데,

처리량이 많거나 동시 접속 수가 많다면, Client Per Process 방식은 좋은 방식은 아닙니다. 그래서 선택하게

되는 것이 Preforked 방식입니다.( Thread 방식에서는 Thread Pool 을 많이 사용합니다. 똑같이 Thread Per Client

나 PreThread 방식도 사용가능합니다. )

그런데 이럴 때, Accept 를 어떻게 할 것인지에 대해서 고민을 하게 됩니다.

보통 일반적으로 쉽게 사용할 수 있는 모델은 Accept Socket 을 생성한 후 Fork 해서 Child 에서 직접적으로

Accept를 하게 되는 모델입니다. Unix Network Programming 의 서버 모델을 참고하세요.

그런데 위의 모델의 경우 Thundering herd problem 이라는 것에 걸리게 됩니다. 같은 Descriptor 에 대해서

Acceptor 나 Select 를 하게 되어서 Event 를 대기하게 된 Process 들이 하나의 Event 가 발생했을 때,

모두 wake up 하게 되어서 서로 경쟁하고 그 중 하나만 Accept 가 성공한 다음 다시 나머지 Process 들이 Sleep

해야 되는 이슈가 발생하게 되는 겁니다. 그래서 Unix Network Programming 의 책을 보게 되면, Parent 에서만

Accept 를 하고 실제 accept 된 socket 은 CHILD 에게 passing 하므로써 해당 Process 가 해당 Client 를 처리하게

하는 모델이 가장 좋다고 제안합니다.(첫번 째 모델로, Lock 을 이용해서 전체 중에 Lock을 획득한 하나만 Accept 를 하게

하는 방법도 있습니다.)

Thread 라면 굉장히 쉽게 File Descriptor 전달이 쉽습니다. 그런데 Process 간의 전달은 조금 힘이듭니다.

Advanced Progamming in Unix Environment 에 보면 해당 방법이 나와있는데, 예제 코드만 보면 이해가 어려워서

특히 Python 에서 어떻게 하는지 어렵기 때문에 간단한 샘플을 만들어보았습니다. 결과만 보면 쉬운걸 고생했습니다.

먼저 Python 에서 직접전으로 FD 전달이 안되고 linux 에서는 UnixDomianSocket 을 이용한 sendmsg, recvmsg 를

사용해야 합니다. 검색을 해보면 이것외에, fcntl.ioctl 을 이용하는 방법들이 있는데 linux 에서는 안되더군요. T.T
(왜 2.6에서도 안되는 T.T)

단 이미, 선지자들이 sendmsg, recvmsg binding 을 만들어 두어서 쉽게 사용할 수 있습니다.

python-passfd 를 설치하면 됩니다.

#!/usr/bin/env python

import os
import sys
import subprocess
import fcntl
import SocketServer
import socket, time
from passfd import sendfd, recvfd
import _passfd

m_max_process_cnt = 10
m_cur_process_cnt = 0
os_pipe_list = []

count = 0
class SocketHandler(SocketServer.StreamRequestHandler):
    """
    """

    def handle(self):
        global count
        sock = self.request
        if count == 10:
            count = 0

        pout = (os_pipe_list[count])
        print "pipe out" , str(pout), sock.fileno()
        try:
            sendfd( pout, sock )
            sock.close()
        except Exception, e:
            print e

        count += 1

def child_do(index, pin):
    'child_do()'

    try:
        while True:
            'do'
            fd, m = recvfd(pin);
            print index, fd, m
            sock = socket.fromfd(fd,1,1)
            sock.send("1234")
            sock.close()
            return

            while True:
                data = sock.recv(1024)
                if not data:
                    print "closesocket: ", sock.fileno()
                    sock.close()
                    break
                else:
                    sock.send(data)
    except:
        sys.exit(1)

def parent_do():
    'parent_do()'
    try:
        server = SocketServer.TCPServer( ("localhost", 8000), SocketHandler )
        server.serve_forever()
    except:
        sys.exit(1)

if __name__ == "__main__":

    for i in range(m_max_process_cnt):
        (pin, pout) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
        print pin, pout

        pid = os.fork()

        if pid == 0:
            pin.close()
            child_do(i, pout)
        else:
            pout.close()
            os_pipe_list.append(pin)

    parent_do()

    sys.exit(0)

–rpath 와 동적라이브러리 링킹

 가끔씩은 코딩만 해서는 해결 할 수 없는 문제들이 있습니다.

(뭐, 해결할 수 없다라기 보다는 시간이 많이드는, 열라 힘든?)

 

-rpath 라는 것이 이런식으로 알면 굉장히 쉬운 일이라고 할 수 있습니다.

우리는 종적 동적라이브러리를 사용합니다. 일명 .so 라고 부르는 파일들이죠.

 

.so를 사용하기 위해서는 명시적으로 dlopen 을 이용하든지, 아니면 그냥 해당 라이브러리의 이름만

링크되어 있는 라이브러리 들을 이용해서 실제 자동적으로 실행시에 해당 .so가 로드되도록 하는 방법이

있습니다.

 

 그런데, 우리는 보통 이 .so를 사용하기 위해서, 경로 지정에 두 가지 방법을 사용합니다.

첫째는 /etc/ld.so.conf 를 수정해서 /sbin/ldconfig 을 이용해서 전역적으로 지정하는 방식이고, 두번째는

LD_LIBRARY_PATH 를 셋팅해서 해당 유저에게만 사용하는 방법입니다.

 

 위의 방식의 문제점은 무엇일까요? 그건, 사용자가 .so 의 위치를 어떤식으로든 지정을 해줘야 하는 겁니다.

같이 배포하더라도, 해당 위치를 지정해야 한다면, 그렇게 쉬운일이 아닙니다.(조금만 알면 쉽지요.) 그런데

-rpath 라는 넘은 프로그램이 빌드될 때, 자동적으로 .so의 위치를 부여하는 것입니다. 절대 상대 경로 모두 사용 가능하구요.

 

  -rpath .so 경로 -Wl 이런식으로 이용할 수 있습니다.

 

이렇게 되면 실행시에 지정된 경로에서 바로 .so 파일을 찾게 됩니다. 배포가 더 쉬워지고

사용하기도 더 쉬워지는 겁니다.

 

 그런데, 전, 이걸 모르고 있었습니다. 그러니, 라이브러리 링크 하는데 -_- 계속 .so 가

없다라는 에러가 T.T 아무리 /etc/ld.so.conf 를 지정해줘도 안되길래 고민하다 보니

이런 특성이 있더군요.

 

 여러분들도 조심하시기 바랍니다.