/************************************************************************
 * AUTHOR: NiuJiuRu
 * FILENAME: swsignal.c
 * DESCRIPTION: 信号量
 * NOTE: 主要用于状态通知
 * HISTORY:
 * 1, [2010-12-17] created by NiuJiuRu
 * 2, [2025-11-24] 优化修改"sw_signal_wait()"函数, 避免依赖系统时间, 导致的
 *                 超时误差或跳变问题
 ***********************************************************************/
#include "swapi.h"
#include "swmem.h"
#include "swsignal.h"

/* 创建信号量 */
void *sw_signal_create()
{
	sem_t *sem = NULL;
	
	sem = (sem_t *)sw_heap_malloc(sizeof(sem_t));
	if(sem)
	{
		memset(sem, 0, sizeof(sem_t));
		sem_init(sem, 0, 0);
	}
	
	return sem;
}

/* 销毁信号量 */
void sw_signal_destroy(void *hSignal)
{
	sem_t *sem = (sem_t *)hSignal;
	sem_destroy(sem);
	sw_heap_free(sem);
}

/* 等待信号量, timeout(ms) = -1时表示无限等待 */
int sw_signal_wait(void *hSignal, int timeout)
{
	sem_t *sem = (sem_t *)hSignal;
	int ret;
	
	if(timeout < 0)
	{
wait_p1:
		ret = sem_wait(sem);
		if(ret < 0 && errno == EINTR) goto wait_p1;
		else return ret;
	}
	else
	{
#if (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30)
		struct timespec ts, now;
		clock_gettime(CLOCK_MONOTONIC/*单调时钟, 不受系统时间影响*/, &now);
		ts.tv_sec = now.tv_sec + timeout/1000;
		ts.tv_nsec = now.tv_nsec + (timeout%1000)*1000*1000;
		if(ts.tv_nsec >= (1000*1000*1000))
		{
		  ts.tv_sec += ts.tv_nsec/(1000*1000*1000);
		  ts.tv_nsec %= (1000*1000*1000);
		}
wait_p2:
		ret = sem_clockwait(sem, CLOCK_MONOTONIC, &ts); // 非POSIX标准, GNU扩展, 需定义:"_GNU_SOURCE"支持
		                                                // , 同时glic库版本需大于等于2.30
		if(ret < 0 && errno == EINTR) goto wait_p2;
		else return ret;
#else
		struct timespec start, now; struct timespec ts = { 0, 1000*1000 };
		clock_gettime(CLOCK_MONOTONIC, &start);
		while((ret = sem_trywait(sem)) < 0 && errno == EAGAIN)
		{
		  clock_gettime(CLOCK_MONOTONIC, &now);
		  long elapsed = (now.tv_sec - start.tv_sec)*1000 + (now.tv_nsec-start.tv_nsec)/(1000*1000);
		  if(elapsed >= timeout) { errno = ETIMEDOUT; return -1; }
			nanosleep(&ts, NULL);
		}
		return ret;
#endif
	}
}

/* 点亮信号量 */
void sw_signal_give(void *hSignal)
{
	sem_t *sem = (sem_t *)hSignal;
	sem_post(sem);
}
